首先本题是求取完全图的最小生成树,但是显然暴力不了
我们观察到任意两点之间的权值就是两个点到根节点的异或和
因此想到用Trie来维护这个操作(反正我想不到)也就是xor最小生成树
我们把每个点到根节点的异或和当做点权值,建立01Trie
这么做是为了下一步做贪心的准备,因为最小生成树其实就是用一些边权较小的点把集合变成联通
我们模仿这个思想,我们把首位不同的点当做是不同集合,从宏观上来说就是01trie的两个分叉,对于子树继续分叉,初始情况是每个叶节点都是一个单独的集合。这样能够不重不漏的划分
并且发现,对于链接这两个集合的最小代价就是在两个集合找两个点使得异或和最小,因此可以设计一个vector保存每个分叉下面有哪些点
上述操作就是经典的trie树上贪心求最小的操作。
我们发现,递归的进行同样的操作,就能把所有节点并成一个集合,并且连接的时候都是用权值最小的一条边,这其实就是最小生成树。
本题最有思维难度的就是转化成异或生成树的过程,如果以前做过,这题就能做了
#includeusing namespace std; typedef long long ll; const int inf=0x3f3f3f3f; const int N=2e5+10; int h[N],ne[N],e[N],w[N],idx; int lf[31*N]; vector<int> rf[31*N]; struct node{ node *nxt[2]; int cnt; }*rt; node pool[31*N]; int top; int v[N]; ll ans; void add(int a,int b,int c){ e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++; } void dfs(int u,int fa,int x){ int i; v[u]=x; for(i=h[u];i!=-1;i=ne[i]){ int j=e[i]; if(j==fa) continue; dfs(j,u,x^w[i]); } } void insert(int x){ node *p=rt; for(int i=29;i>=0;i--){ int sign=(x>>i)&1; if(p->nxt[sign]==NULL){ p->nxt[sign]=pool+(++top); p->nxt[sign]->cnt=top; } if(sign==1) rf[p->cnt].push_back(x); else lf[p->cnt]++; p=p->nxt[sign]; } } ll now; void cal(node *p,int depth,int x){ for(int i=depth;i>=0;i--){ ll sign=x>>i&1; if(p->nxt[sign]!=NULL){ p=p->nxt[sign]; } else{ now+=1<<i; p=p->nxt[!sign]; } } } void solve(node *p,int depth){ if(lf[p->cnt]&&(int)rf[p->cnt].size()!=0){ ll mi=1e18; for(auto x:rf[p-> cnt]){ now=1<<depth; cal(p->nxt[0],depth-1,x); mi=min(mi,now); } ans+=mi; } if(p->nxt[0]!=NULL) solve(p->nxt[0],depth-1); if(p->nxt[1]!=NULL) solve(p->nxt[1],depth-1); } int main(){ rt=pool; int n; rt->cnt=0; cin>>n; int i; memset(h,-1,sizeof h); for(i=1;i ){ int a,b,c; cin>>a>>b>>c; a++,b++; add(a,b,c); add(b,a,c); } dfs(1,-1,0); for(i=1;i<=n;i++){ insert(v[i]); } solve(rt,29); cout< endl; }