1. 程式人生 > 實用技巧 >Xor-MST

Xor-MST

題意

給出一個 \(n\) 個點的無向完全圖,每個點的點權為:\(a_i\),每條邊的權值為該邊兩個端點的點權的異或值。求出這個圖最小生成樹的權值。

\(1\leq n \leq 200000,0\leq a_i < 2^{30}\)

題目連結:https://codeforces.com/problemset/problem/888/G

分析

最小異或生成樹,把點權儲存在字典樹上進行匹配。

程式碼

#include <bits/stdc++.h>
#define pb push_back
using namespace std;
typedef long long ll;
const int N=2e5+5;
const int maxn=3e6+5;
int a[N],trie[maxn][2],cnt;
int id[maxn];
vector<int>value[N];
ll ans;
void add(int x,int k)
{
    int rt=1;
    for(int i=29;i>=0;i--)
    {
        int t=((x>>i)&1);
        if(trie[rt][t]==0)
            trie[rt][t]=++cnt;
        rt=trie[rt][t];
    }
    id[rt]=k;
    value[k].pb(x);
}
int matching(int x,int rt,int d)
{
    int res=(1<<d);
    for(int i=d-1;i>=0;i--)
    {
        int t=((x>>i)&1);
        if(trie[rt][t]>0)
            rt=trie[rt][t];
        else
        {
            rt=trie[rt][1-t];
            res|=(1<<i);
        }
    }
    return res;
}
void solve(int rt,int d)//注意d的取值,字典樹以邊表示二進位制位的值
{
    if(trie[rt][0]>0) solve(trie[rt][0],d-1);
    if(trie[rt][1]>0) solve(trie[rt][1],d-1);
    if(trie[rt][0]>0&&trie[rt][1]>0)//分叉點
    {
        int x=id[trie[rt][0]],y=id[trie[rt][1]];
        int min_xor=(1<<30);
        if(value[x].size()<value[y].size())//選取小的子樹
        {
            for(int i=0;i<value[x].size();i++)
            {
                int tmp=value[x][i];
                int xr=matching(tmp,trie[rt][1],d-1);
                min_xor=min(xr,min_xor);
                value[y].pb(tmp);
            }
            id[rt]=y;
        }
        else
        {
            for(int i=0;i<value[y].size();i++)
            {
                int tmp=value[y][i];
                int xr=matching(tmp,trie[rt][0],d-1);
                min_xor=min(xr,min_xor);
                value[x].pb(tmp);
            }
            id[rt]=x;
        }
        ans+=min_xor;
    }
    else
    {//上傳到父親節點
        if(trie[rt][0]>0||trie[rt][1]>0)
            id[rt]=id[trie[rt][0]+trie[rt][1]];
    }
}
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    sort(a+1,a+1+n);
    cnt=1;
    add(a[1],1);
    for(int i=2;i<=n;i++)
    {
        if(a[i]!=a[i-1])
            add(a[i],i);
    }
    ans=0;
    solve(1,30);
    printf("%lld\n",ans);
    return 0;
}