poj 1182 食物链 && nyoj 207(种类并查集)

来源:互联网 发布:北京儿童编程培训机构 编辑:程序博客网 时间:2024/05/16 09:45
食物链
Time Limit: 1000MS Memory Limit: 10000KTotal Submissions: 52414 Accepted: 15346

Description

动物王国中有三类动物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),输出假话的总数。 

Input

第一行是两个整数N和K,以一个空格分隔。 
以下K行每行是三个正整数 D,X,Y,两数之间用一个空格隔开,其中D表示说法的种类。 
若D=1,则表示X和Y是同类。 
若D=2,则表示X吃Y。

Output

只有一个整数,表示假话的数目。

Sample Input

100 71 101 1 2 1 22 2 3 2 3 3 1 1 3 2 3 1 1 5 5

Sample Output

3

题意为:有A、B、C三种动物,A吃B,B吃C,C吃A,并给出一些条件,判断为你提供信息的人说了多少句假话。
并查集,通常用来检查两个元素是否在同一集合中,或者是将两个不同的集合为一个集合。
至于这道题,有神人曰,利用并查集,同时对每个节点保持其到根结点的相对类别偏移量,定义为:
               0——同类;
               1——食物;
               2——天敌。

一个很详细的解题报告,看得我五体投地:点击打开链接

这里他讲的异常详细,哪一点都有讲到,,其中60行到180行就有下边注释的公式的解析

好厉害,,2015,7,27

#include<stdio.h>#define M 50005int x[M],re[M];void init(){for(int i=0;i<M;i++){x[i]=i; re[i]=0;}}int find(int k){int temp=x[k];if(x[k]==k) return k;x[k]=find(x[k]);re[k]=(re[k]+re[temp])%3;//( 儿子的关系 + 父亲的关系 ) % 3 = 儿子对爷爷的关系return x[k];}void merge(int a,int b,int fa,int fb,int d){x[fa]=fb;re[fa]=(re[b]-re[a]+d+3)%3;//d是a与b的关系,(3-re[a])是a为根节点时,他父亲的关系,(re[b]-re[a]+d+3)%3就是a的根节点与他的父亲就是b的根节点的关系 }int main(){int n,m,a,b,d,fa,fb,count=0;scanf("%d%d",&n,&m);init();while(m--){scanf("%d%d%d",&d,&a,&b);if( a>n || b>n || (a==b && d==2) ){count++;continue;}fa=find(a);fb=find(b);if(fa==fb&&(re[a]-re[b]+3)%3!=d-1)//3-re[b]就得到了根节点和b的关系,re[a]+3-re[b]就是a关于b的关系  count++;elsemerge(a,b,fa,fb,d-1);}printf("%d\n",count);return 0;}
还有另外一种比较巧妙的简单的方法:
对于每只动物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是否在同一组等信息。
(一开始我一直不明白,对于两种信息都是合并,
那么以后怎么分清到底是同类还是捕食关系呢,或者说如何判断是否会产生矛盾呢?
后来发现,它利用3*N的数组分3段1~N,N~2N,2N~3N分别当做是A、B、C三个种类的集合,
把所有可能符合的情况都会导入进去,虽然对于两种信息的操作都是合并,
但合并的内容是不一样的,这样就可以在合并之前判断其是否以另一种信息合并过或者符合另一种信息。可以自己举例来理解一下)
#include<stdio.h>#define M 50005*3int x[M];void init(){for(int i=0;i<M;++i){x[i]=i;}}int find(int k){if(x[k]==k) return k;x[k]=find(x[k]);return x[k];}void merge(int a,int b){int fa=find(a); int fb=find(b);if(fa!=fb) x[fa]=fb;}bool same(int a,int b){return find(a)==find(b);}int main(){int n,m,a,b,c,count=0;scanf("%d%d",&n,&m);init();while(m--){scanf("%d%d%d",&c,&a,&b);//元素a,a+N,a+2*N分别代表a-A,a-B,a-C if(a>n || b>n ||(a==b && c==2)) {count++;continue;}if(c==1){if(same(a,b+n) || same(a,b+2*n)) count++;//对于第一种信息是不能出现捕食与被捕食关系的  else{merge(a,b);merge(a+n,b+n);merge(a+2*n,b+2*n);}}else{if(same(a,b) || same(a,b+2*n)) count++;//不能出现捕食同类和反捕食的情况 else{merge(a,b+n);merge(a+n,b+2*n);merge(a+2*n,b);}}}printf("%d\n",count);return 0; } 


0 0
原创粉丝点击