ICPC沈阳B题 —— 带权并查集

ICPC沈阳

今天打比赛的题目,很快想到了方法但并查集写不对,太菜了,调很久才过

题意

给出n,m条限制,每条限制形如u, v, w,表示a[u] xor a[v] = w,构造一个数组a使得这n个数的和最小,若无法构造则输出-1.

思路 

显然,如果不在m条限制中,则直接置0即可,否则对于每条限制,若两个点不在一个集合里,把两个点所在集合合并,每个集合存储一个根节点,z[i]存储第i个点与根节点的xor值,通过带权并查集把w转化成z[i],具体见代码。

否则若z[x] xor z[y] != w,则输出-1。

求和的时候按位枚举每一位,显然对于任意一个集合,每一位的1的数量只有两种情况:因为都转化成了与根的关系,只要根的关系给定了(只能是0或1),其他的也就确定了,对于每一位取两种情况的小的一种即可。

时间复杂度O(nlogw)                         w < (1<<30)

#include<bits/stdc++.h>
using namespace std;
int n, m;
const int maxn = 1e5 + 10;
int fa[maxn];
int vis[maxn];
int cnt[maxn];
int vis2[maxn];
vector<int> a[maxn];
int z[maxn];	
int w;
int find(int x) {
	if(x == fa[x]) return x;
	int root = find(fa[x]);
	z[x] = z[x] ^ z[fa[x]];
	return fa[x] = root;
} 
void merge(int x, int y) {
	int fx = find(x), fy = find(y);
	if(fx != fy) {
		fa[fx] = fy;
// 		z[fx] = z[fx] ^ z[fy] ^ w;
		z[fx] = z[x] ^ z[y] ^ w;
	}
}
int main() {
	scanf("%d%d", &n, &m);
	bool ok = true;
	for(int i = 1; i <= n; i++) {
		fa[i] = i;
	}
	for(int i = 1; i <= m; i++) {
		int u, v;
		scanf("%d%d%d", &u, &v, &w);
        vis[u] = vis[v] = 1;
		if(find(u) == find(v)) {
//			cerr << u << ' ' << z[u] << find(u)<< endl;
//			cerr << v << ' ' << z[v] << find(v)<<endl;
			if((z[u] ^ z[v]) != w) ok = false;
		}
		else {
			merge(u, v);
		}
	}
	if(!ok) {
		printf("-1\n");
		return 0;
	}
    for(int i = 1; i <= n; i++) find(i);
	int p = 0;
	for(int i = 1; i <= n; i++) {
		if(!vis[i]) continue;
		int fx = find(i);
		if(!vis2[fx]) vis2[fx] = ++p;
		a[vis2[fx]].push_back(z[i]);
	}
	long long ans = 0;
	for(int i = 1; i <= p; i++) {
		int q = (int)a[i].size();
		for(int k = 0; k < 31; k++){
			int tmp = 0;
			for(int j = 0; j < q; j++) {
				if(a[i][j] & (1LL << k)) tmp++;
			}
			int minn = min(tmp, q - tmp);
			ans = ans + (long long)minn * (1LL << k);
		}
	}
	printf("%lld\n", ans);
	return 0;
}
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值