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表示该集合中元素之和。

则最后的结果为:(注意每个集合只需要被算一遍)

\[res= ∑2^k×min(sz[find(i)],sz[find(i+n)]) \]

代码


#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

posted @ 2022-09-16 21:36  kingwzun  阅读(77)  评论(0编辑  收藏  举报