nyist 207&&poj 1182 食物链(并查集)



食物链

时间限制: 1000  ms  |  内存限制: 65535  KB
难度: 5
描述
动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形。A吃B, B吃C,C吃A。 
现有N个动物,以1-N编号。每个动物都是A,B,C中的一种,但是我们并不知道它到底是哪一种。 
有人用两种说法对这N个动物所构成的食物链关系进行描述: 
第一种说法是"1 X Y",表示X和Y是同类。 
第二种说法是"2 X Y",表示X吃Y。 
此人对N个动物,用上述两种说法,一句接一句地说出K句话,这K句话有的是真的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。 
1) 当前的话与前面的某些真的话冲突,就是假话; 
2) 当前的话中X或Y比N大,就是假话; 
3) 当前的话表示X吃X,就是假话。 
你的任务是根据给定的N(1 <= N <= 50,000)和K句话(0 <= K <= 100,000),输出假话的总数。 
输入
第一行是两个整数N和K,以一个空格分隔。  
以下K行每行是三个正整数 D,X,Y,两数之间用一个空格隔开,其中D表示说法的种类。  
若D=1,则表示X和Y是同类。  
若D=2,则表示X吃Y。
输出
只有一个整数,表示假话的数目。
样例输入
100 7
1 101 1 
2 1 2
2 2 3 
2 3 3 
1 1 3 
2 3 1 
1 5 5
样例输出
3
来源
POJ
上传者
iphxer

这道题可以算的上是很经典的并查集的题目了,刚开始做的时候也没思路啊,各种wa啊,后来参考了大神的思路http://blog.csdn.net/niushuai666/article/details/6981689,也看了好久,才把那个更新域搞懂。

这个题目,最重要的是处理关系域的更新,两个集合合并了之后,合并成一个集合,之间的关系就变了,就要更新他们之间的关系,其他的地方就和普通的并查集一样,这里才是并查集的本质吧。

#include <stdio.h>
#define MAXN 10000
struct node
{
    int father;
    int relation;//定义他们之间的关系
};
node s[MAXN];
int find(int x)//带动态压缩的查找
{
    int temp;
    if(x==s[x].father)
        return x;
    temp=s[x].father;
    s[x].father=find(temp);
    s[x].relation=(s[x].relation+s[temp].relation)%3;//关系域的更新,要找准他们之间的关系
    return s[x].father;
}
int main()
{
   int n,k,d,x,y,i,sum=0;
   int root1,root2;
   scanf("%d%d",&n,&k);
   for(i=0;i<n;i++)//初始化
   {
       s[i].father=i;
       s[i].relation=0;
   }
   for(i=0;i<k;i++)
   {
       scanf("%d%d%d",&d,&x,&y);
       if(x>n||y>n)
       {
           sum++;
           continue;
       }
       if(d==2&&x==y)
       {
             sum++;
             continue;
       }
       root1=find(x);
       root2=find(y);
       if(root1!=root2)//合并操作
       {
           s[root2].father=root1;
           s[root2].relation=(3+(d-1)+s[x].relation-s[y].relation)%3;//域的更新
       }
       else
       {
           if(d==1&&s[x].relation!=s[y].relation)
           {
             sum++;
             continue;
           }
           if(d==2&&((3-s[x].relation+s[y].relation)%3!=d-1))
           {
                sum++;
                continue;
           }
       }
   }
   printf("%d\n",sum);
    return 0;
}

这是一种做法,看到挑战程序设计竞赛里面的有另一种做法。这里也可以用不带秩的合并(内存可以少一点)。

#include<stdio.h>
#include<string.h>
 #define MAX 10000
 int parent[MAX],rank[MAX];//父亲,树的高度
int n,k,cnt;
 int find(int r)
 {
 int p=r;
 while(p!=parent[p]) p=parent[p];
 while(r!=p)
 {int temp=parent[r];parent[r]=p;r=temp; }
 return p;
 }
 void unionn(int r1,int r2)
 {
   r1=find(r1),r2=find(r2);
   if(rank[r1]<rank[r2])
   {parent[r1]=r2;      }
   else
   {
    parent[r2]=r1;
    if(rank[r1]==rank[r2]) rank[r1]++;//因为r2的父节点变成了r1,深度+1
   }
 }
 bool same(int a,int b)
 {
 return find(a)==find(b); //相等表示同一个集合
}
int main()
 {
 while(~scanf("%d%d",&n,&k))
 {
   cnt=0;
   for(int i=0;i<=3*n;i++)//这里赋值到3*n,一开始没注意到,然后结果也不对
  {
    parent[i]=i;
   }
   memset(rank,0,sizeof(rank));
   for(int i=0;i<k;i++)
   {
   int d,r1,r2;
   scanf("%d%d%d",&d,&r1,&r2);
   if(r1>n||r2>n)
   { cnt++;continue; }
  /*以下要做的,就是r1表示A种类,r1+n表示B,r1+2*n表示C ,r2同理。
   
   */
   if(d==1)//同类
  {
    if(same(r1,r2+n)||same(r1,r2+2*n) )
   /*这里是处理同类的2种情况,
     假如r1(A种类)和r2(B种类)是同一个集合,那么这肯定是假话
     假如r1(A种类)和r2(C种类)是同一个集合,也是假话
   */
    cnt++;
    else
    {unionn(r1,r2);unionn(r1+n,r2+n);unionn(r1+2*n,r2+2*n); }
   /*假如是真话,它们就是同类,分别合成一个集合,A与A,B与B,C与C */
   }
   else//x吃y
   {
    if(same(r1,r2)||same(r1,r2+2*n) )
     /*这里是处理x吃y的2种情况,
     假如r1(A种类)和r2(A种类)是同一个集合,不能同类相吃 ,是假话
     假如r1(A种类)和r2(C种类)是同一个集合,也是假话 ,只有C种类吃A种类,A种类只能吃B种类。
   */
    cnt++;
    else
    {unionn(r1,r2+n);unionn(r1+n,r2+2*n);unionn(r1+2*n,r2); }
   /*假如是真话,那么就让x吃y的关系合成一个集合,A种类吃B,B吃C,C吃A */
   }
   }
   printf("%d\n",cnt);
   
 }
  
 return 0;
 }


感觉还是太菜了,有些细节还是处理的不好,理解跟自己能够完全做出来还是两码事啊。。。  

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