[UOJ 176]新年的繁荣

一、题目

点此看题

二、解法

考虑 k r u s k a l kruskal 算法,我们不能去枚举每一条边,但是边权较小,我们可以从大到小枚举边权。设当前枚举到的边权为 i i ,我们定义 a [ i ] a[i] 为二进制下包含子集 i i 的权值,我们可以用 O ( m ) O(m) 的时间把以前的 a a 拿过来用(枚举 a [ i   a n d   2 j ] a[i\space and\space 2^j] 直到找到值),然后去枚举连的边,查找 i   a n d   2 j i\space and\space 2^j 是否存在,如果存在就合并查集,然后贡献答案,时间复杂度 O ( m 2 m ) O(m2^m)

注意一开始时直接贪心合并值相同的项,剩下来操作的都是值不同的,详见代码。

#include <cstdio>
#define int long long
const int N = 18;
const int M = 1<<N;
int read()
{
	int x=0,flag=1;char c;
	while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
	while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
	return x*flag;
}
int n,m,ans,a[M],p[M];
int findSet(int x)
{
	if(x^p[x]) p[x]=findSet(p[x]);
	return p[x];
}
int merge(int x,int y)
{
	if(findSet(x)==findSet(y)) return 0;
	p[findSet(x)]=findSet(y);
	return 1;
}
signed main()
{
	n=read();m=read();
	for(int i=1;i<=n;i++)
	{
		int x=read();
		if(a[x]) ans+=x;
		else a[x]=x;
	}
	for(int i=1;i<M;i++)
		p[i]=i;
	for(int i=M-1;i>=1;i--)
	{
		for(int j=0;j<N && (!a[i]);j++)
			a[i]=a[i|(1<<j)];
		for(int j=0;j<m;j++)
			if(a[i|(1<<j)] && merge(a[i],a[i|(1<<j)]))
				ans+=i;
	}
	printf("%lld\n",ans);
}
发布了217 篇原创文章 · 获赞 12 · 访问量 5149

猜你喜欢

转载自blog.csdn.net/C202044zxy/article/details/104065398