題目連接git
有一張\(n\)個點的徹底圖,每一個點的權值爲\(a_i\),兩個點之間的邊權爲\(a_i\ xor\ a_j\)。求該圖的最小生成樹。
\(n\leq2*10^5,0\leq ai<2^{30}\)。算法
代碼好神啊。優化
依舊是從高到低考慮每一位。對於當前位i,若是全部點在這一位都爲0或1,不須要管(任何邊在這一位都爲0)。
不然能夠把點分爲兩個集合,即i位爲0和1的集合,這兩個集合間必須存在一條邊,且邊權這一位只能爲1。spa
考慮怎麼高效獲得兩個集合間的最小邊。能夠將一個集合的\(a_i\)插入Trie,再枚舉另外一個集合的點在Trie上走。
這樣枚舉每一位而後合併兩個集合的點,再遞歸到兩邊(該位爲0或1),就能夠獲得MST了。
這也是Borůvka算法的過程,不過用Trie能夠將每次需\(O(m)\)的迭代優化到\(O(n\log a_{max})\)。code
實現細節:能夠先對全部點建Trie,並直接在Trie樹上DFS,存在左右兒子時即會分爲兩個集合。
將\(a_i\)從小到大插入Trie,這樣可對每一個節點維護一個區間,表示 知足根到該節點01取值 的序列下標區間。這樣枚舉時就不須要暴力\(O(n)\)了。遞歸
複雜度\(O(n\log n\log a_{max})\)。基本到不了吧。(或者我分析錯了吧)ip
//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; }