[Codeforces] 888G. Xor-MST Boruvka算法/分治+01trie

8 篇文章 0 订阅
4 篇文章 1 订阅

Solution

经典的异或最小生成树,我所知道的有两个做法,分别介绍一下。
1、最小生成树的Boruvka算法。
这个最小生成树算法大概流程是把开始的 n n n个点视为 n n n个连通块,然后每次每个连通块找到一条连向其他连通块的权值最小的边并连起来,这样每次至少减少一半的联通块数,复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn)。那么在这题套用这个算法的话,可以建一个所有数的 01 01 01trie,每次求一个连通块连出去的最小边时在trie上删掉这个连通块中的数,然后枚举连通块中的数,在 01 01 01trie上找异或最小值即可。复杂度是 O ( 30 n log ⁡ n ) O(30n\log n ) O(30nlogn)
2、分治做法。
还是挺巧妙的,按位考虑,按最高位为 0 / 1 0/1 0/1分成两个集合,一定是 0 0 0的集合互相连, 1 1 1的集合互相连,然后两边连一条最小边,那么只要用上面找两个集合中的数异或起来最小值的方法,就能递归下去变成子问题了。复杂度是一样的。

Code(做法一)

#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define pa pair<int,int>
const int Maxn=200010;
const int inf=2147483647;
int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return x*f;
}
int n,a[Maxn],fa[Maxn];LL ans=0;
int findfa(int x){return ((fa[x]==x)?x:fa[x]=findfa(fa[x]));}
int cnt[Maxn*32],tot=0,son[Maxn*32][2],id[Maxn*32];
void ins(int y)
{
	int x=a[y];
	int cur=0;
	for(int i=29;i>=0;i--)
	{
		int t=(x>>i)&1;
		if(!son[cur][t])son[cur][t]=++tot;
		cnt[cur=son[cur][t]]++;
	}
	id[cur]=y;
}
void del(int y)
{
	int x=a[y];
	int cur=0;
	for(int i=29;i>=0;i--)
	{
		int t=(x>>i)&1;
		cnt[cur=son[cur][t]]--;
	}
}
int v,p;
void query(int x)
{
	int cur=0,y=x;v=0;
	for(int i=29;i>=0;i--)
	{
		int t=(x>>i)&1;
		if(cnt[son[cur][t]])cur=son[cur][t];
		else v|=(1<<i),cur=son[cur][t^1];
	}
	p=id[cur];
}
vector<int>h[2],g[Maxn];
int main()
{
	n=read();
	for(int i=1;i<=n;i++)a[i]=read();
	sort(a+1,a+1+n);n=unique(a+1,a+1+n)-(a+1);
	for(int i=1;i<=n;i++)ins(i);
	for(int i=1;i<=n;i++)fa[i]=i,g[i].push_back(i);
	int o=0;
	for(int i=1;i<=n;i++)h[o].push_back(i);
	while(h[o].size()!=1)
	{
		o^=1;
		for(int i=0;i<h[o^1].size();i++)
		{
			int x=h[o^1][i];
			for(int j=0;j<g[x].size();j++)del(g[x][j]);
			int mn=inf,w;
			for(int j=0;j<g[x].size();j++)
			{
				query(a[g[x][j]]);
				if(v<mn)mn=v,w=p;
			}
			int fx=findfa(x),fy=findfa(w);
			if(fx!=fy)
			{
				fa[fx]=fy;
				ans+=(LL)mn;
			}
			for(int j=0;j<g[x].size();j++)ins(g[x][j]);
		}
		h[o].clear();
		for(int i=1;i<=n;i++)g[i].clear();
		for(int i=1;i<=n;i++)
		{
			int x=findfa(i);
			g[x].push_back(i);
			if(x==i)h[o].push_back(x);
		}
	}
	printf("%lld",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值