\(Description\)
有一張\(n\)個點的完全圖,每個點的權值為\(a_i\),兩個點之間的邊權為\(a_i\ xor\ a_j\)。求該圖的最小生成樹。
\(n\leq2*10^5,0\leq ai<2^{30}\)。
\(Solution\)
代碼好神啊。
依舊是從高到低考慮每一位。對於當前位i,如果所有點在這一位都為0或1,不需要管(任何邊在這一位都為0)。
否則可以把點分為兩個集合,即i位為0和1的集合,這兩個集合間必須存在一條邊,且邊權這一位只能為1。
考慮怎么高效得到兩個集合間的最小邊。可以將一個集合的\(a_i\)插入Trie,再枚舉另一個集合的點在Trie上走。
這樣枚舉每一位然后合並兩個集合的點,再遞歸到兩邊(該位為0或1),就可以得到MST了。
這也是Borůvka算法的過程,不過用Trie可以將每次需\(O(m)\)的迭代優化到\(O(n\log a_{max})\)。
實現細節:可以先對所有點建Trie,並直接在Trie樹上DFS,存在左右兒子時即會分為兩個集合。
將\(a_i\)從小到大插入Trie,這樣可對每個節點維護一個區間,表示 滿足根到該節點01取值 的序列下標區間。這樣枚舉時就不需要暴力\(O(n)\)了。
復雜度\(O(n\log n\log a_{max})\)。基本到不了吧。(或者我分析錯了吧)
//171ms 98200KB
#include <cstdio>
#include <cctype>
#include <algorithm>
//#define gc() getchar()
#define MAXIN 300000
#define gc() (SS==TT&&(TT=(SS=IN)+fread(IN,1,MAXIN,stdin),SS==TT)?EOF:*SS++)
#define BIT 29
typedef long long LL;
const int N=2e5+5;
int read();
char IN[MAXIN],*SS=IN,*TT=IN;
struct Trie
{
#define ls son[x][0]
#define rs son[x][1]
#define S N*31
int n,A[N],tot,son[S][2],L[S],R[S];
LL Ans;
#undef S
void Insert(int v,int id)
{
int x=0;
for(int i=BIT; ~i; --i)
{
int c=v>>i&1;
if(!son[x][c]) son[x][c]=++tot, L[tot]=R[tot]=id;
x=son[x][c];
L[x]=std::min(L[x],id), R[x]=std::max(R[x],id);
}
}
int Query(int x,int v,int bit)
{
if(bit<0||L[x]==R[x]) return A[L[x]];//同樣注意第0位還可以繼續遞歸==
int c=v>>bit&1;
return son[x][c]?Query(son[x][c],v,bit-1):(son[x][c^1]?Query(son[x][c^1],v,bit-1):0);
}
void DFS(int x,int bit)
{
// if(bit<0) return;
if(!bit)
{
if(ls&&rs) Ans+=A[L[ls]]^A[L[rs]];//第0位還會有分叉
return;
}
if(ls&&rs)
{
int res=0x7fffffff;
for(int i=L[ls],r=R[ls],p=rs; i<=r; ++i)
res=std::min(res,A[i]^Query(p,A[i],bit-1));
Ans+=res;
}
if(ls) DFS(ls,bit-1);
if(rs) DFS(rs,bit-1);
}
void Solve()
{
n=read();
for(int i=1; i<=n; ++i) A[i]=read();
std::sort(A+1,A+1+n);
for(int i=1; i<=n; ++i) Insert(A[i],i);
DFS(0,BIT), printf("%I64d\n",Ans);
}
}T;
inline int read()
{
int now=0;register char c=gc();
for(;!isdigit(c);c=gc());
for(;isdigit(c);now=now*10+c-'0',c=gc());
return now;
}
int main()
{
T.Solve();
return 0;
}