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;
}