POJ 1182 食物链(带权并查集)

题目链接:http://poj.org/problem?id=1182

题目大意:有N只动物,分别编号为1~N,所有动物都属于A,B,C中的其中一种。已知A吃B、B吃C、C吃A。按顺序给出下面的两种信息共K条。

     第一种,x和y属于同一种类。

     第二种,x吃y。

当这些信息满足以下任意一个条件时,该信息为假信息:

     ① 当前的话与前面的某些真的话冲突,就是假话。
     ② 当前的话中X或Y比N大,就是假话。
     ③ 当前的话表示X吃X,就是假话。 

需要你判断一共有多少条信息是假的。 

解题思路:

解法一:数组扩增三倍用来表示A,B,C。这种解法我是在《挑战程序设计》上看的,感觉是我比较好理解的版本。下面都是书上的原话:

对于每只动物x创建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是否在同一组等信息。

代码:

 1 #include<cstdio>
 2 const int N=5e4+5;
 3 
 4 int root[N*3];
 5 
 6 int find(int x){
 7     return root[x]==x?x:root[x]=find(root[x]);
 8 }
 9 
10 bool same(int a,int b){
11     return find(a)==find(b);
12 }
13 
14 void unite(int a,int b){
15     if(find(a)!=find(b))
16         root[find(a)]=find(b);
17 }
18 
19 //元素x,x+N,x+2*N分别代表x-A,x-B,x-C 
20 int main(){
21     int N,K,ans=0;
22     scanf("%d%d",&N,&K);
23     for(int i=1;i<=3*N;i++){
24         root[i]=i;
25     }
26     while(K--){
27         int t,x,y;
28         scanf("%d%d%d",&t,&x,&y);
29         if(x>N||y>N){
30             ans++;
31             continue;
32         }
33         if(t==1){
34             //"x,y属于同一类" 的信息 
35             if(same(x,y+N)||same(x,y+2*N))
36                 ans++;
37             else{
38                 unite(x,y);
39                 unite(x+N,y+N);
40                 unite(x+2*N,y+2*N);
41             }
42         }
43         else{
44             //"x吃y"的信息 
45             if(same(x,y)||same(x,y+2*N))
46                 ans++;
47             else{
48                 unite(x,y+N);
49                 unite(x+N,y+2*N);
50                 unite(x+2*N,y);
51             }
52         }
53     }
54     printf("%d\n",ans);
55 }

 解法二:这是网上流传比较广的一种解法,好像叫向量法,相比较于第一种解法,更省空间,因为如解法一如果种类数量过多肯定MLE的。解法如下:

    开设一个val[]数据用于记录每个点所属的种类,0 1 2分别表示A,B,C。0->1->2->0,那么当 :    

    a->b 偏移量0时 a和b同类

    a->b 偏移量1时 a吃b

    a->b 偏移量2时 a被b吃,也就是b吃a

一开始这个数组为0,所有的点都是独立的,不是相连的,没有关系。慢慢加入点之后,把有关系的合并在一起,并且编号的相对大小确定一个集合中的关系。注意:一定要单组输入,否则会WA

还有关于路径压缩时val数组的更新,为了保证偏移量,比如有"x吃y"信息,root[y]=x,val[x]=0,val[y]=1偏移量为1。若出现"z吃x"信息 ,则val[x]=1,此时通过使val[y]=val[x]+val[y]=2,来保证x和y之间的偏移量。

关于val[t2]=(val[x]-val[y]+3)%3这句,利用val[t2]使得val[y]+val[t2]=val[x]+1,以满足"x吃y ",因为val[t2]最后会在路径压缩的过程中作用到它的子节点x上。

代码:

 1 #include<cstdio>
 2 const int N=5e4+5;
 3 
 4 int root[N],val[N];
 5 
 6 int find(int x){
 7     if(root[x]==x)
 8         return x;
 9     int tmp=find(root[x]);
10     val[x]=(val[x]+val[root[x]])%3;//为了保证偏移量,比如有"x吃y"信息,root[y]=x,val[x]=0,val[y]=1偏移量为1。 
11     return root[x]=tmp;            //若出现"z吃x"信息 ,则val[x]=1,此时val[y]=val[x]+val[y]=2,保证了原来的偏移量。 
12 }
13 
14 int main(){
15     int n,k,ans=0;
16     scanf("%d%d",&n,&k);
17     for(int i=1;i<=n;i++){
18         root[i]=i;
19     }
20     while(k--){
21         int t,x,y;
22         scanf("%d%d%d",&t,&x,&y);
23         if(x>n||y>n){
24             ans++;
25             continue;
26         }
27         int t1=find(x),t2=find(y);
28         //检查x和y是否已经存在关系 
29         if(t1==t2){
30             if(t==1&&val[x]!=val[y]) 
31                 ans++;
32             if(t==2&&(val[x]+1)%3!=val[y])
33                 ans++;
34             continue;
35         }
36         if(t==1){
37             root[t2]=t1;                //合并 
38             val[t2]=(val[x]-val[y]+3)%3;//val[t2]为x->y的偏移量,利用val[t2]使得val[y]+val[t2]=val[x]+1,以满足"x吃y "。 
39         }
40         else{
41             root[t2]=t1;                //同理 
42             val[t2]=(val[x]-val[y]+1+3)%3;
43         }
44     }
45     printf("%d\n",ans);
46     return 0;
47 }

 

posted @ 2017-10-04 21:37  Yeader  阅读(236)  评论(0编辑  收藏  举报