poj1182(维护并查集里更复杂的关系)

链接:http://poj.org/problem?id=1182
题意:有N只动物,1~N,每只动物只能属于A,B,C三类中的一类,A,B,C 满足 A吃B,B吃C,C吃A,的关系。
给出两种类型的关系,问那些是假的。
第一种,1,x,y 表示x,y 属于一种类型的动物
第二种 ,2,x,y 表示x吃y。
假的 信息有三种情况:
1.x,y>N
2. 关系2 时,x与y相等
3. 和前面的话冲突

思路:由于并查集只能反应属于同一集合的关系,而本题不只有属于同一类,有三类情况,这里我们就要思考,怎么样连接他们的关系能达到检验三种关系,并且可以传递。
这里技巧是:
对于每只动物i创建3个元素i-A, i-B, i-C, 并用这3*N个元素建立并查集。这个并查集维护如下信息:
① i-x 表示 “i属于种类x”。
②并查集里的每一个组表示组内所有元素代表的情况都同时发生或不发生。
例如,如果i-A和j-B在同一个组里,就表示如果i属于种类A那么j一定属于种类B,如果j属于种类B那么i一定属于种类A。因此,对于每一条信息,只需要按照下面进行操作就可以了。
1)第一种,x和y属于同一种类———合并x-A和y-A、x-B和y-B、x-C和y-C。
2)第二种,x吃y—————————合并x-A和y-B、x-B和y-C、x-C和y-A。
不过在合并之前需要先判断合并是否会产生矛盾。例如在第一种信息的情况下,需要检查比如x-A和y-B或者y-C是否在同一组等信息。
为什么这样可行了,我觉得首先这样的组合正好可以区分三种类别,最重要他们关系还可以传递,没有被破坏,当然就行(自行忽略我的解释…).实在迷糊,你可以举个例试试,就可以发现其中技巧。

#include <fstream>
#include <iostream>
#include <string>
#include <cstring>
#include <complex>
#include <math.h>
#include <set>
#include <vector>
#include <map>
#include <queue>
#include <stdio.h>
#include <stack>
#include <algorithm>
#include <list>
#include <ctime>
#include <ctime>
#include <assert.h>

#define rep(i,a,n) for (int i=a;i<n;i++)
#define per(i,a,n) for (int i=n-1;i>=a;i--)
#define pb push_back
#define mp make_pair
#define all(x) (x).begin(),(x).end()
#define fi first
#define se second
#define eps 1e-8
#define M_PI 3.141592653589793

typedef long long ll;
const ll mod=1000000007;
const int inf=0x7fffffff;
ll powmod(ll a,ll b) {ll res=1;a%=mod;for(;b;b>>=1){if(b&1)res=res*a%mod;a=a*a%mod;}return res;}
using namespace std;
const int N=2000;
int f[N*2+10],n,m;
void init()
{
    for(int i=1;i<=N*2;i++) f[i]=i;
}

int Find(int x)
{
    return f[x]==x? x: f[x]=Find(f[x]);
}
void Merge(int x,int y)
{
    x=Find(x);y=Find(y);
    if(x!=y) f[x]=y;
}

int main()
{
   int T;scanf("%d",&T);
   int t=0;
   while(T--){
      int flag=0;
      scanf("%d%d",&n,&m);
      init();
      for(int i=0;i<m;i++){
          int a,b;scanf("%d %d",&a,&b);
          if(Find(a)==Find(b)&&Find(a+N)==Find(b+N)){
              flag=1;
          }
          else{
            Merge(a+N,b);Merge(a,b+N);
          }
      }
      if(flag) printf("Scenario #%d:\nSuspicious bugs found!\n\n",++t);
      else printf("Scenario #%d:\nNo suspicious bugs found!\n\n",++t);
   }
}

后来百度别人题解,想看看大牛的解释,发现基本都用一种我不知道的做法,带权并查集,作用就是原有几何关系上,再次区分更复杂归属关系,用法更是有技巧,比挑战做法复杂些…我先照着打了一遍,似乎还未完全领悟。 以后再次用到,再写用法…

#include<cstdio>
#include<cstring>
using namespace std;
const int N=50000;
int f[N+10],rak[N+10];
void init()
{
    for(int i=1;i<=50000;i++){
        f[i]=i;
    }
    memset(rak,0,sizeof(rak));
}

int Find(int x){
  if(x!=f[x]){
      int fx=Find(f[x]);
      rak[x]=(rak[x]+rak[f[x]])%3;
      f[x]=fx;
  }
  return f[x];
}

bool Merge(int x,int y,int t)
{
    int tx=Find(x),ty=Find(y);
    if(tx==ty){
        if((rak[y]-rak[x]+3)%3!=t) return true;
        else return false;
    }
    f[ty]=tx;
    rak[ty]=(rak[x]-rak[y]+t+3)%3;
    return false;
}

int main()
{
    int N,M,ans=0;
    init();
    scanf("%d%d",&N,&M);
    for(int i=0;i<M;i++){
       int a,b,d; scanf("%d%d%d",&d,&a,&b);
       if(a<=0|b<=0||a>N||b>N||(d==2&&a==b)){
           ans++;
           continue;
       }
       if(Merge(a,b,d-1)) ans++;
    }
    printf("%d\n",ans);
}

发现同样类型的题目还有 poj1703 hdu1829 当然我用的都是《挑战》上技巧AC..

你可能感兴趣的:(并查集)