2021 ICPC 沈阳赛区B题 Bitwise Exclusive-OR Sequence 二进制 | 扩展域并查集 | DFS
题意 :
给n个点,m条边,每条边的边权为w,是其连边两点的异或和,求出满足题意的图的最小点权和,如果不存在这样的图则输出-1
基本思路
前提知识:
-
异或的传递性质 :有,则
推论 :有,...,则 -
异或的转换性质 :有则
结合以上两个性质 :有
也就是每一块连通区域内的其他任意点的点权与基点的点权的dfs关系
实现1:扩展域并查集
如果分析到上面的结论,则是扩展域并查集的裸题,对二进制的每一位都做一遍并查集(做30次)即可。
若
-
时,表示和在第k 位的二进制相反。
则将和合并,将和合并。 -
时,表示a_u在第k 位的二进制相同。
则将和合并,将和合并。 -
当合并出现矛盾时,返回−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
本文作者:kingwzun
本文链接:https://www.cnblogs.com/kingwz/p/16701287.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
2021-09-16 补题*总结题21/9/14