2021 ICPC 沈阳赛区B题 Bitwise Exclusive-OR Sequence 二进制 | 扩展域并查集 | DFS
题意 :
给n个点,m条边,每条边的边权为w,是其连边两点的异或和,求出满足题意的图的最小点权和,如果不存在这样的图则输出-1
基本思路
前提知识:
-
异或的传递性质 :有\(a \oplus b = x, b \oplus c = y\),则\(a \oplus c = x \oplus y\)
推论 :有\(a_1 \oplus a_2 = w_1. a_2 \oplus a_3 = w_2\),...,则\(a_1 \oplus a_n = w_1 \oplus w_2 \oplus ... \oplus w_{n-1}\) -
异或的转换性质 :有\(a \oplus b = c\)则\(a \oplus c = b\)
结合以上两个性质 :有\(a_n=a_1 \oplus w_1 \oplus w_2 \oplus ... \oplus w_{n-1}\)
也就是每一块连通区域内的其他任意点的点权与基点的点权的dfs关系
实现1:扩展域并查集
如果分析到上面的结论,则是扩展域并查集的裸题,对二进制的每一位都做一遍并查集(做30次)即可。
若\(a_u\oplus a_v=w\)
-
\((w >> k)\&1 = 1\)时,表示\(a_u\)和\(a_v\)在第k 位的二进制相反。
则将\(f[u]\)和\(f[v+n]\)合并,将\(f[u+n]\)和\(f[v]\)合并。 -
\((w >> k)\&1 = 0\)时,表示a_u\(和a_v\)在第k 位的二进制相同。
则将\(f[u]\)和\(f[v]\)合并,将\(f[u+n]\)和\(f[v+n]\)合并。 -
当合并出现矛盾时,返回−1。
-
其中每次合并要维护一个sz表示该集合中元素之和。
则最后的结果为:(注意每个集合只需要被算一遍)
代码
#include <bits/stdc++.h>
#define int long long
#define pii pair<int, int>
using namespace std;
const int N=2e5 + 10;
int fa[N];
int n,m;
int a[N];
int sz[N];
vector<tuple<int,int,int> > vec;
int find(int x){
if(fa[x]!=x) fa[x]=find(fa[x]);
return fa[x];
}
int power(int a, int b) {
int res = 1;
while (b) {
if (b & 1) res *= a;
a *= a;
b >>= 1;
}
return res;
}
void solve()
{
int flag=1;
cin>>n>>m;
for(int i=0;i<m;i++){
int x,y,w;
cin>>x>>y>>w;
vec.push_back({x,y,w});
}
int ans=0;
for(int i=0;i<=30;i++){
for(int j=0;j<=n;j++){
fa[j]=j,fa[j+n]=j+n;
sz[j] = 1, sz[j + n] = 0;
}
int x,y,w;
for(int j=0;j<m;j++){
tie(x,y,w)=vec[j];
// cout<<x<<" "<<y<<" "<<w<< " "<<flag<<endl;
w=(w& (1<<i));
int x1=find(x);
int x2=find(x+n);
int y1=find(y);
int y2=find(y+n);
if((x1==y1 && w) || (x1==y2 && w==0) ){
// cout<<i<<" "<<j<<" fdfdd "<<x1<<" "<<y1<<endl;
flag=0;
break;
}
else{
if(w){
if(x1==y2) continue;
fa[x1]=y2;
fa[x2]=y1;
sz[y2] += sz[x1];
sz[y1] += sz[x2];
}
else{
if(x1==y1) continue;
fa[x1]=y1;
fa[x2]=y2;
sz[y1] += sz[x1];
sz[y2] += sz[x2];
}
}
}
for (int j = 1; j <= n; j++) {
ans += min(sz[find(j)], sz[find(j + n)]) * power(2, i);
sz[find(j)] = 0;
sz[find(j + n)] = 0;
}
}
if(flag==0 ) cout<<-1<<endl;
else cout<<ans<<endl;
}
signed main()
{
cin.tie(nullptr) -> sync_with_stdio(false);
int t = 1;
// cin >> t;
while (t--)
solve();
return 0;
}
实现2:DFS
考虑本题,根据题意建图,考虑每一块连通区域,若合法,则只需对其中任意一点确定权值,则能确定连通块中每一点的权值,并且任选其中一个点为该连通块的基点,能确定该连通块内任何一点与基点的异或和.
因此,我们可以从1开始对所有未经过的点dfs,
- dfs过程中若某点已经被访问过且无法拥有唯一确定点权(成环了),则输出-1,且退出遍历
- 否则在dfs过程中建立以本次dfs基点为中心的菊花图,边权为基点与目标点连边的异或和
对每个建的新连通块,基点的每一位都可以枚举0和1两种情况,取更小的
-
如果某一位上这个连通区域内1的个数大于0的个数,基点的这一位就放1,
因为原先异或性质导致一条边的两个端点同一位肯定是同时变化的 -
反之基点就放0
代码
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
#define int long long
const int N=2e5+10;
bool vis[N];
bool flag=true;
int value[N];
int cnt[100];
vector< pair<int,int> > g[N];
vector<int> res[N];
void dfs(int st,int u)
{
vis[u]=true;
for(int i=0;i<g[u].size();i++)
{
int v=g[u][i].first,w=g[u][i].second;
if(vis[v])//已经标记过了
{
if(value[v]!=(value[u]^w)) flag=0;
}
else
{
value[v]=value[u]^w;
res[st].push_back(v);
dfs(st,v);
}
}
}
int add(int k)
{
memset(cnt,0,sizeof(cnt));
int ans=0;
int len=res[k].size();
for(int i=0;i<=30;i++)
{
for(int j=0;j<len;j++)
{
int v=res[k][j];
if((value[v]>>i)&1) cnt[i]++;
}
if(cnt[i]>len/2) ans+=(len-cnt[i]+1)*(1<<i);
else ans+=cnt[i]*(1<<i);
}
return ans;
}
signed main()
{ios_base::sync_with_stdio(0);
int n,m;
cin>>n>>m;
for(int i=1;i<=m;i++)
{
int u,v,w;
cin>>u>>v>>w;
g[u].push_back({v,w});
g[v].push_back({u,w});
}
int ans=0;
for(int i=1;i<=n;i++)
{
if(flag && vis[i]==false)
{
dfs(i,i);
ans+=add(i);
}
}
if(flag) cout<<ans<<endl;
else cout<<-1<<endl;
return 0;
}
————————————————
版权声明:本文为CSDN博主「MoYan1082」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_45874328/article/details/121459476
————————————————
版权声明:本文为CSDN博主「Viktoriae」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/m0_51448653/article/details/121569794