Loading

题解 CF888G Xor-MST

题意

给定一张完全图,点有点权,边权定义为两端点的边权异或和,求最小生成树的边权和

\(n\le 10^5\)

题解

这是一张完全图,发现克鲁斯卡尔算法完全行不通,且我们不可能去处理出所有的边权

考虑 借鉴 Boruvka 算法思想。就是将每一轮从连通块连出一条最小的通往其他连通块的边,并将两个连通块连在一起。可以发现最多只会进行\(\log n\)

重点在于求出每个连通块的最小出边。

在这道题里面,我们可以用01trie 来维护连通块的最小出边,具体做法是每次找连通块在trie树上的另一个数使其异或值最小,复杂度\(O(n\log n \log a_i)\)

再考虑一下我们是怎么合并两个连通块的,可以发现如若在trie树上一个节点存在分叉,那么就必然会是两个不同的联通块会在此处产生"连边"

于是考虑从上到下遍历trie树,找出两个连通块的数的集合,然后求出最小出边即可,复杂度\(O(n\log a)\)

代码

#include<bits/stdc++.h>
using namespace std;
int const MAXN=2e5+10;
int n;
int a[MAXN],L[MAXN*30],R[MAXN*30];
struct Trie{
	int tot=1;
	int trie[MAXN*30][2];
	void insert(int pos){
		int x=1;
		for(int i=30;;i--){
			int dir=(a[pos]>>i)&1;
			L[x]=min(L[x],pos),R[x]=max(R[x],pos);
			if(i<0)break;
			if(!trie[x][dir])trie[x][dir]=++tot;
			x=trie[x][dir];
		}
		return;
	}
	long long query(int x,int now,int dep){
		if(dep<0)return 0ll;
		int dir=(x>>dep)&1;
		if(trie[now][dir])return query(x,trie[now][dir],dep-1);
		else return query(x,trie[now][dir^1],dep-1)+(1ll<<dep);
	}
	long long dfs(int now,int dep){
		if(dep<0)return 0;
		// printf("%d %d %d %d\n",now,trie[now][0],trie[now][1],dep);
		if(trie[now][0]&& trie[now][1]){
			long long ans=1ll<<30;
			// printf("%d %d\n",L[trie[now][0]],R[trie[now][1]]);
			for(int i=L[trie[now][0]];i<=R[trie[now][0]];i++){
				ans=min(ans,(long long)query(a[i],trie[now][1],dep-1));
			}
			ans+=dfs(trie[now][0],dep-1);
			ans+=dfs(trie[now][1],dep-1);
			ans+=(1<<dep);
			return ans;
		}
		else if(trie[now][0]){return dfs(trie[now][0],dep-1);}
		else if(trie[now][1]){return dfs(trie[now][1],dep-1);}
		return 0ll;
	}
}trie;
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	sort(a+1,a+n+1);
	memset(L,0x3f,sizeof(L));
	for(int i=1;i<=n;i++)trie.insert(i);
	printf("%lld\n",trie.dfs(1,30));
	return 0;
}
posted @ 2021-03-26 21:37  fpjo  阅读(54)  评论(0编辑  收藏  举报