codeforces 888G 题解

题目传送门

题目大意: n n n 个点,每个点有一个权值,两个点之间边的长度为两点权值的异或值,求最小生成树的边权和。

题解

Boruvka \text{Boruvka} Boruvka 算法拿来用,计算两个连通块的最短边时,分别维护出两块的 01  trie 01~\text{trie} 01 trie,遍历其中一个就可以算出最短边,具体来说就是贪心,从最高位开始使每一位尽可能相同。然后合并两块时,将他们的 trie \text{trie} trie 也合并起来,那么最后就会得到一棵包含所有数的 trie \text{trie} trie

这种方法的问题在于,没办法每次都求出两两间最短边的长度,进一步的,就没法求出一个连通块连出去的最短边,但是提供了一个很好的思路,即只需要确定连通块的合并顺序,就容易求出答案了。

考虑包含所有数的那颗 trie \text{trie} trie,他的一个节点的子树就对应一个点集,容易发现,从下往上对于每个点合并他的左右子树,这种策略是最优的合并顺序,因为这样可以保证,每个连通块在合并时,一定是沿着最短的边与别人合并的。

于是每次只需要求左右子树的合并最短边即可,大力把左子树内的每个点丢到右子树内跑贪心即可,时间复杂度 O ( n log ⁡ 2 n ) O(n\log^2n) O(nlog2n)

代码如下:

#include 
#include 
#include 
using namespace std;
#define maxn 200010
#define inf 999999999
#define ll long long

int n,a[maxn];
struct node{
     
	int l,r,dep;
	node *ch[2];
	node(int Dep):l(inf),r(0),dep(Dep){
     ch[0]=ch[1]=NULL;}
}*root;
void insert(int x){
     
	node *now=root;
	for(int i=29;i>=0;i--){
     
		int c=a[x]>>i&1;
		if(!now->ch[c])now->ch[c]=new node(i-1);
		now=now->ch[c];
		now->l=min(now->l,x);
		now->r=max(now->r,x);
	}
}
int ask(node *now,int x){
     
	if(now->dep<0)return 0;
	int c=x>>(now->dep)&1;
	if(now->ch[c])return ask(now->ch[c],x);
	else return ask(now->ch[c^1],x)+(1<<(now->dep));
}
ll solve(node *now){
     
	if(now->ch[0]&&now->ch[1]){
     
		int mi=inf;
		for(int i=now->ch[0]->l;i<=now->ch[0]->r;i++)mi=min(mi,ask(now->ch[1],a[i]));
		return solve(now->ch[0])+solve(now->ch[1])+mi+(1<<now->dep);
	}
	if(now->ch[0])return solve(now->ch[0]);
	if(now->ch[1])return solve(now->ch[1]);
	return 0;
}

int main()
{
     
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	sort(a+1,a+n+1);root=new node(29);
	for(int i=1;i<=n;i++)insert(i);
	printf("%lld",solve(root));
}

你可能感兴趣的:(题解_杂)