cf
luogu
这题\(prim\)和\(kruskal\)似乎都不可做,考虑\(Boruvka\)算法,维护一堆连通块,对于每个连通块每次找出其他连通块和它的最小权值的边,然后只用这些边合并连通块,首先这样子做是对的,因为参考\(prim\),连通块应该用最小权的边和其他连通块合并,并且每次合并连通块数量至少少一半,所以只要做\(logn\)次.至于找其他连通块到它的最小权值的边,可以考虑\(trie\),每次把自己联连通块内的点在\(trie\)上的点删掉,然后枚举连通块内每个点查询与其他点的异或最小值
#include
#define LL long long
#define uLL unsigned long long
#define db double
using namespace std;
const int N=2e5+10;
int rd()
{
int x=0,w=1;char ch=0;
while(ch<'0'||ch>'9'){if(ch=='-') w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
return x*w;
}
int n,a[N],ff[N],stk[N][3],tp;
int findf(int x){return ff[x]==x?x:ff[x]=findf(ff[x]);}
int q[N],hd,tl,op[N*30],tot;
int s[N*30],ch[N*30][2],id[N*30],rt,tt;
vector e[N];
vector::iterator it;
LL ans;
int main()
{
n=rd();
rt=++tt;
int rs=n-1;
for(int i=1;i<=n;++i) a[i]=rd();
sort(a+1,a+n+1);
for(int i=1;i<=n;++i)
{
ff[i]=i;
int o=rt;
++s[o];
for(int j=29;~j;--j)
{
int xx=a[i]>>j&1;
if(!ch[o][xx]) ch[o][xx]=++tt;
o=ch[o][xx];
++s[o];
}
if(id[o]) ff[id[o]]=i,e[i].push_back(id[o]),--rs;
id[o]=i;
}
while(rs)
{
for(int i=1;i<=n;++i)
if(findf(i)==i)
{
hd=1,q[tl=1]=i;
while(hd<=tl)
{
int x=q[hd++],o=1;
op[++tot]=o;
--s[o];
for(int j=29;~j;--j)
{
int xx=a[x]>>j&1;
o=ch[o][xx];
--s[o];
op[++tot]=o;
}
for(it=e[x].begin();it!=e[x].end();++it)
q[++tl]=*it;
}
int zz=0,mi=1<<30;
hd=1,q[tl=1]=i;
while(hd<=tl)
{
int x=q[hd++],o=1;
for(int j=29;~j;--j)
{
int xx=a[x]>>j&1;
if(s[ch[o][xx]]) o=ch[o][xx];
else o=ch[o][xx^1];
}
if(mi>(a[x]^a[id[o]])) mi=a[x]^a[id[o]],zz=id[o];
for(it=e[x].begin();it!=e[x].end();++it)
q[++tl]=*it;
}
stk[++tp][0]=i,stk[tp][1]=findf(zz),stk[tp][2]=mi;
while(tot) ++s[op[tot]],--tot;
}
while(tp)
{
int x=findf(stk[tp][0]),y=findf(stk[tp][1]),z=stk[tp][2];
if(x!=y)
{
ans+=z;
ff[y]=x;
e[x].push_back(y);
--rs;
}
--tp;
}
}
printf("%lld\n",ans);
return 0;
}