并查集算法

食物链

时间限制: 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,也看了好久,才把那个更新域搞懂。

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

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. #include <stdio.h>  
  2. #define MAXN 10000  
  3. struct node  
  4. {  
  5.     int father;  
  6.     int relation;//定义他们之间的关系  
  7. };  
  8. node s[MAXN];  
  9. int find(int x)//带动态压缩的查找  
  10. {  
  11.     int temp;  
  12.     if(x==s[x].father)  
  13.         return x;  
  14.     temp=s[x].father;  
  15.     s[x].father=find(temp);  
  16.     s[x].relation=(s[x].relation+s[temp].relation)%3;//关系域的更新,要找准他们之间的关系  
  17.     return s[x].father;  
  18. }  
  19. int main()  
  20. {  
  21.    int n,k,d,x,y,i,sum=0;  
  22.    int root1,root2;  
  23.    scanf("%d%d",&n,&k);  
  24.    for(i=0;i<n;i++)//初始化  
  25.    {  
  26.        s[i].father=i;  
  27.        s[i].relation=0;  
  28.    }  
  29.    for(i=0;i<k;i++)  
  30.    {  
  31.        scanf("%d%d%d",&d,&x,&y);  
  32.        if(x>n||y>n)  
  33.        {  
  34.            sum++;  
  35.            continue;  
  36.        }  
  37.        if(d==2&&x==y)  
  38.        {  
  39.              sum++;  
  40.              continue;  
  41.        }  
  42.        root1=find(x);  
  43.        root2=find(y);  
  44.        if(root1!=root2)//合并操作  
  45.        {  
  46.            s[root2].father=root1;  
  47.            s[root2].relation=(3+(d-1)+s[x].relation-s[y].relation)%3;//域的更新  
  48.        }  
  49.        else  
  50.        {  
  51.            if(d==1&&s[x].relation!=s[y].relation)  
  52.            {  
  53.              sum++;  
  54.              continue;  
  55.            }  
  56.            if(d==2&&((3-s[x].relation+s[y].relation)%3!=d-1))  
  57.            {  
  58.                 sum++;  
  59.                 continue;  
  60.            }  
  61.        }  
  62.    }  
  63.    printf("%d\n",sum);  
  64.     return 0;  
  65. }  

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

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. #include<stdio.h>  
  2. #include<string.h>  
  3.  #define MAX 10000  
  4.  int parent[MAX],rank[MAX];//父亲,树的高度  
  5. int n,k,cnt;  
  6.  int find(int r)  
  7.  {  
  8.  int p=r;  
  9.  while(p!=parent[p]) p=parent[p];  
  10.  while(r!=p)  
  11.  {int temp=parent[r];parent[r]=p;r=temp; }  
  12.  return p;  
  13.  }  
  14.  void unionn(int r1,int r2)  
  15.  {  
  16.    r1=find(r1),r2=find(r2);  
  17.    if(rank[r1]<rank[r2])  
  18.    {parent[r1]=r2;      }  
  19.    else  
  20.    {  
  21.     parent[r2]=r1;  
  22.     if(rank[r1]==rank[r2]) rank[r1]++;//因为r2的父节点变成了r1,深度+1  
  23.    }  
  24.  }  
  25.  bool same(int a,int b)  
  26.  {  
  27.  return find(a)==find(b); //相等表示同一个集合  
  28. }  
  29. int main()  
  30.  {  
  31.  while(~scanf("%d%d",&n,&k))  
  32.  {  
  33.    cnt=0;  
  34.    for(int i=0;i<=3*n;i++)//这里赋值到3*n,一开始没注意到,然后结果也不对  
  35.   {  
  36.     parent[i]=i;  
  37.    }  
  38.    memset(rank,0,sizeof(rank));  
  39.    for(int i=0;i<k;i++)  
  40.    {  
  41.    int d,r1,r2;  
  42.    scanf("%d%d%d",&d,&r1,&r2);  
  43.    if(r1>n||r2>n)  
  44.    { cnt++;continue; }  
  45.   /*以下要做的,就是r1表示A种类,r1+n表示B,r1+2*n表示C ,r2同理。 
  46.     
  47.    */  
  48.    if(d==1)//同类  
  49.   {  
  50.     if(same(r1,r2+n)||same(r1,r2+2*n) )  
  51.    /*这里是处理同类的2种情况, 
  52.      假如r1(A种类)和r2(B种类)是同一个集合,那么这肯定是假话 
  53.      假如r1(A种类)和r2(C种类)是同一个集合,也是假话 
  54.    */  
  55.     cnt++;  
  56.     else  
  57.     {unionn(r1,r2);unionn(r1+n,r2+n);unionn(r1+2*n,r2+2*n); }  
  58.    /*假如是真话,它们就是同类,分别合成一个集合,A与A,B与B,C与C */  
  59.    }  
  60.    else//x吃y  
  61.    {  
  62.     if(same(r1,r2)||same(r1,r2+2*n) )  
  63.      /*这里是处理x吃y的2种情况, 
  64.      假如r1(A种类)和r2(A种类)是同一个集合,不能同类相吃 ,是假话 
  65.      假如r1(A种类)和r2(C种类)是同一个集合,也是假话 ,只有C种类吃A种类,A种类只能吃B种类。 
  66.    */  
  67.     cnt++;  
  68.     else  
  69.     {unionn(r1,r2+n);unionn(r1+n,r2+2*n);unionn(r1+2*n,r2); }  
  70.    /*假如是真话,那么就让x吃y的关系合成一个集合,A种类吃B,B吃C,C吃A */  
  71.    }  
  72.    }  
  73.    printf("%d\n",cnt);  
  74.      
  75.  }  
  76.     
  77.  return 0;  
  78.  } 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值