题目链接:http://codeforces.com/problemset/problem/888/G
题意:
给你1e5个点,每个点有一个权值,两个点之间如果连边,那么边权就是这两个点权异或起来的值,要你求出所有点组成的最小生成树。
做法:
说实话我真的不可能想的到,哪怕知道了分治的做法,也是花了一段时间去理解的。
总的来说就是把所有的数字先进行排序,把所有的数字用01字典树进行维护。假设数字 1 2 3 4 -> 01 10 11 100
用最高不同位将数字进行分开,之后内部的数字再用同样的方法进行异或连边,比如100和01 10 11 从第3位就不同,那么100就要在1 10 11 中找一个进行异或,1和10 11 在第二位不同,之后 10和11先做,后和1做,最后和100做,复杂度是logn的,每次进行查找还需要30,理论上也是不会炸的。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=(ll)1e9+7;
const int maxn=200005;
ll ans=0;
int trie[maxn*30][2],tot,n,a[maxn];
void Insert(int x){
int rt=0;
for(int i=29;i>=0;i--){
int now=(x>>i)&1;
if(!trie[rt][now]) trie[rt][now]=++tot;
rt=trie[rt][now];
}
}
int Search(int x){
int ans=0,rt=0;
for(int i=29;i>=0;i--){
int now=(x>>i)&1;
if(trie[rt][now]){
rt=trie[rt][now];
}
else{
rt=trie[rt][now^1];
ans|=(1<<i);
}
}
return ans;
}
void dfs(int l,int r,int dep){
if(dep==-1||l>=r) return ;
int mid=l-1;
while(mid<r&&((a[mid+1]>>dep)&1)==0) mid++;
dfs(l,mid,dep-1);
dfs(mid+1,r,dep-1);
if(mid==l-1||mid==r) return ;
for(int i=l;i<=mid;i++) {
Insert(a[i]);
}
int tmp=INT_MAX;
for(int i=mid+1;i<=r;i++) {
tmp=min(tmp,Search(a[i]));
}
ans+=(ll)tmp;
for(int i=0;i<=tot;i++) {
trie[i][0]=trie[i][1]=0;
}
tot=0;
}
int main(){
scanf("%lld",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
sort(a+1,a+1+n);
dfs(1,n,29);
printf("%lld\n",ans);
}