1. 程式人生 > >CF888G Xor-MST 解題報告

CF888G Xor-MST 解題報告

CF888G Xor-MST

題意翻譯

給定一個\(n\)個節點的完全圖,每個節點有個編號\(a_i\),節點\(i\)和節點\(j\)之間邊的權值為\(a_i\ xor\ a_j\),求該圖的最小生成樹的權值和。

說明

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


有一種B開頭的MST演算法

大致流程是這樣的

當前每個集合伸出去一條最短的邊,然後把聯通塊縮成一個新的集合,因為每次縮集合個數減半,所以複雜度對。

事實上基本不會讓我們真去寫它,有時候需要用這種方法或者思想處理處MST。

比如這個題就可以這麼做,每次合併實際上對trie樹做啟發式合併,然後在裡面查一下最小值。

然後有一種神奇的思路,這裡sto attack巨佬

發現每次合併的集合都是最高位的1不同的兩個集合進行合併,於是可以從上往下做,從最高位把集合分開,然後查詢兩個集合的最小連邊。

如果我們把所有元素按從小到大排序加入trie,那麼一個集合內的元素就在一個連續的區間裡啦

這樣我們就可以非常方便的直接遍歷一遍字典樹統計出答案了


Code:

#include <cstdio>
#include <cstring>
#include <algorithm>
#define ll long long
using std::min;
using std::max;
const int N=2e5+10;
const int inf=0x3f3f3f3f;
#define ls ch[now][0]
#define rs ch[now][1]
int L[N*40],R[N*40],ch[N*40][2],tot;
int n,a[N],root;
void Insert(int &now,int x,int dep)
{
    if(!now) now=++tot;
    L[now]=min(L[now],x),R[now]=max(R[now],x);
    if(dep<0) return;
    int bit=a[x]>>dep&1;
    Insert(ch[now][bit],x,dep-1);
}
int query(int now,int val,int dep)
{
    if(dep<0) return 0;
    int bit=val>>dep&1;
    if(ch[now][bit]) return query(ch[now][bit],val,dep-1);
    else return query(ch[now][bit^1],val,dep-1)+(1<<dep);
}
ll dfs(int now,int dep)
{
    if(dep<0) return 0;
    if(R[ls]&&R[rs])
    {
        int mi=inf;
        for(int i=L[ls];i<=R[ls];i++) mi=min(mi,query(rs,a[i],dep-1));
        return dfs(ls,dep-1)+dfs(rs,dep-1)+mi+(1<<dep);
    }
    if(R[ls]) return dfs(ls,dep-1);
    if(R[rs]) return dfs(rs,dep-1);
    return 0;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",a+i);
    std::sort(a+1,a+1+n);
    memset(L,0x3f,sizeof(L));
    for(int i=1;i<=n;i++) Insert(root,i,30);
    printf("%lld\n",dfs(root,30));
    return 0;
}

2019.1.4