POJ1182_食物链_并查集

来源:互联网 发布:游戏编程软件中文版 编辑:程序博客网 时间:2024/05/29 17:56

唔,写了一晚上- -不只是写代码,我以为之前那个HDU4003我写的注释已经够多的了,没想到这次写的更多,果然写注释是会上瘾的- -

直接贴题和我自己的代码吧,题解神马的全在代码里,自认为自己这次写的还是比较清楚详尽和简洁的(自大啦-_-)

不过主要的核心思想用模三加法来加入并查集并不是我自己想出来的



食物链
Time Limit: 1000MS Memory Limit: 10000KTotal Submissions: 35163 Accepted: 10227

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

觉得应该把程序的用时和内存列出来,以后的程序都这么写吧

Run IDUserProblemResultMemoryTimeLanguageCode LengthSubmit Time11755638chengtbf1182Accepted556K282MSC++2930B2013-07-10 09:07:45

/*此题为并查集的题因为一共只有三种动物,所以无论当前节点v与根节点root之间隔了几个动物(比如按照输入,u与n1有x1关系,n1与n2有x2关系......一直到nk与root之间有xk关系)但是v和root之间总共只可能有3种关系,同类,root吃u,root被u吃所以就可以定义三种状态表示当前结点与根节点的关系(通过父亲节点一步一步迭代得到)0,1,2。利用模3加法就可以简单有效的得到我们要找的u与root之间的关系【很难想啊——】定义同类关系为0,这样相加的时候,+0不会影响不同物种之间的吃与被吃的关系父亲节点吃子节点为1,子节点吃父亲节点为2这样比如有情况1:a->(1)b->(1)c,即为a吃b,b吃c,由食物链得a,b,c分别属于三个物种,所以c吃a得到关系a->(2)c 即为1+1为2,说明成立情况2:a->(2)b->(1)c,即为b吃a,b吃c,所以a,c属于同一物种得到关系a->(0)c 即为2+1为3,模3为0,成立情况3:a->(1)b->(2)c,即为a吃b,c吃b,a,c还是同类,同情况2情况3: a->(2)b->(2)c,即为b吃a,c吃b,则a吃c得到关系a->(1)c,即为(2+2)%3=1,成立由于父亲0不影响祖父与孙子之间的关系,所以没有再举例综上所述,可以用模3加法来推出根节点与所有子节点的关系,即可以用并查集下面是判断真假,第一种情况是a,b同根root,此时真假都可能root->(num_a)aroot->(num_b)b此时题中输入的是a->(num_ab)b若(-num_a+num_b)%3==num__ab时,为真话【可以用上面的方法推出所有情况验证/也可以用向量图,由子节点到跟的情况是负的状态值】否则为假另外一种情况是a、b不在同一颗树中,a的根节点为roota,b的根节点为rootb【此时必为真话,理由是离散中的命题之间的条件语句P->Q若P为F,则命题为T】这时候怎么通过a,b关系导出roota跟rootb之间的关系呢roota->(num_a)arootb->(num_b)b此时题中输入的是a->(num_ab)b则roota->( (num_a+num_ab-num_b)%3 )rootb【可以用上面的方法推出所有情况验证】*/#include<stdio.h>#include<string.h>#define N 50005int parent[N];//parent[]数组用来记录父子关系int val[N];//val[]数组用来记录当i结点为儿子结点时,他的父亲节点与他的关系(0,1,2)int iteration;//这个全局的迭代参量iteration非常关键int find_parent(int p){if (parent[p]!=p){//val[p]=(val[p]+val[parent[p]])%3;//当自己的父亲节点不是自己时,继续找,此时自己与祖父之间的关系就是自己的值加上父亲的值再模3parent[p]=find_parent(parent[p]);iteration=(val[p]+iteration)%3;//这里,直接用之前的方法val[p]=(val[p]+val[parent[p]])%3;来压缩根节点与当前结点的路径是不正确的//由于是先更新关系,后更新父亲,所以导致子节点只能向上更新一代//举例1->2->3->4用上面的方法更新时,先访问4,更新4的val为4+3再模3,此时已经更新完毕,接下来3又会被更新成3+2,//而我们需要的是将4的根节点设为1,然后4里面存4+3+2+1的值,上面的方法达不到,需要改进//于是需要一个迭代变量,当递归访问到根结点时,依次迭代回子节点把子节点的所有祖先的值的和存到iteration里面,保证每次更新是加上所有祖先的和val[p]=iteration;}else{iteration=0;//当递归访问到根结点时,开始返回,初始化为0}return parent[p];}int find_merge(int a,int b,int val_ab){int roota=find_parent(a);int rootb=find_parent(b);if (roota!=rootb)//不相等一定是真话,就直接连就行了{parent[rootb]=roota;val[rootb]=(val[a]-val[b]+val_ab+3)%3;//此处加3是为了保证取模运算里面的值为正数return 1;}else//否则就不一定,需要判断{if ((val[b]-val[a]+3)%3==val_ab)//判断条件在前面的注释中有详解{return 1;}else{return 0;}}}int main(){int n,k,i,counter;int a,b,val_ab;scanf("%d%d",&n,&k);counter=0;memset(val,0,sizeof(val));for ( i = 1; i <=n ; i++){parent[i]=i;//这里给val[]数组和parent[]数组初始化是有讲究的,跟前面的分析一脉相承//把每个结点的父亲节点设为自己,自己和自己是同类,所以val[i]的值为0}for ( i = 0; i <k ; i++){scanf("%d%d%d",&val_ab,&a,&b);val_ab--;//此处调整一下val_ab的值,因为题中定义的吃与被吃和我自己定义的不一样if (a>n||b>n ||( a==b  &&  val_ab!=0 )||find_merge(a,b,val_ab)==0)//分三种情况为假话,第一种是编号大于N,第二种是同类相吃,第三种是不符合关系{//由于前面任何一条满足都会直接进入if语句,所以不用担心不符合条件的编号会进入find_merge()函数counter++;}}printf("%d\n",counter);return 0;}