2.例题1 poj1182 食物链
题目:动物王国中有三类动物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。
**********思路:*************
(1)此题一看是带权并查集,带的权是什么呢,就是他们之间的关系。所以这里建立集合是以二者之间是否建立过关系为依据,即如果二者根相同,即在一个关系圈子里,是一定存在吃于被吃或者同类的关系的!若根不同即二者之间没有关系,则不管是D=1还是=2都要拉近一个关系网里!
(2)对于(2)(3)容易判断:若(D==2&&x==y||x>N||y>N)则sum++;(sum为假话数量)
(3)对于(1):
<1>如果D==1:若x于y的根是一个即在一个圈子里,如果他俩的关系标记relation[x]!=relation[y]的话即不是同类,此时sum++;若x的根!=y的根,根据题意,这句话成为条件句,需要将xy所在的关系网络合并,为下一步判断做准备。在更新根数组pre的同时,也要更新所有节点于根的关系relation!
<2>如果D==2:若x于y的根是一个即在一个圈子里,如果他俩的关系标记不满足x吃y的关系,此时sum++;若x的根!=y的根,根据题意,这句话成为条件句,需要将xy所在的关系网络合并,为下一步判断做准备。在更新根数组pre的同时,也要更新所有节点于根的关系relation!
(4)上面思路有了,接下来对于细节给予描述:
<1>我们定义每个节点与自己这个关系网络集合根的关系(relation数组来标记)为:
relation[x]==0:表示x与根节点为同类; relation[x]==1:表示x被他的根节点吃; relation[x]==2表示x吃他的根节点;
我们注意:这里的0 1 2不是随便的,
如果 d == 1则 x和y 是同类 ,那么 y 对 x 的关系是 0
如果 d == 2 则 x 吃了 y, 那么 y 对 x 的关系是 1, x 对 y 的关系是 2.
综上所述 ,无论 d为1 或者是为 2, y 对 x 的关系都是 d-1
<2>对于xy是否是同类好判断,那么x吃y怎么通过relation数组判断呢?请看下图:
所以若(relation[x]+1)%3==relation[y]表示x指向y也就是x吃y。
<3>相对关系的判断:如果x对y的关系是a,则y对x的关系就是3-a;
<4>在压缩路径过程中,我们也必须压缩关系,在把所有子节点直接连接到根上面的时候,也直接把relaion[x]更新为直接与根的关系,而不是与原来上级的关系。所以我们这里要找出三人之间的关系规律,即已知父亲(上级)与儿子(当前)关系,找到儿子(当前)与爷爷(最终根)的关系,通过穷举发现:
( 儿子relation + 父亲relation ) % 3 = 儿子对爷爷的relation !
有了这个,我们就可以在压缩路径的同时,压缩关系网!
<5>在我们合并集合的时候,在把两个根节点连接的时候也别忘了更新这两个根节点的关系,才能保证整个关系网里面的关系正确!但是我们在合并的时候是已知x与y,只知道x与x集合根的关系,y与y集合根的关系,怎么通过xy找到两集合根之间都关系呢?答案是通过向量!很神奇吧!看图:
所以:综上所述,代码如下:
#include <iostream> //#include<bits/stdc++.h> #include<cstring> #include<cstdio> using namespace std; const int maxn=50000+5; int pre[maxn];//保存前导 int relation[maxn];//保存当前节点与根节点的关系 int N,K; int finded(int a) { if(a==pre[a])return a; int t=pre[a]; pre[a]=finded(pre[a]);//先递归压缩路径,把各节点先连接到根上,这样才会最多出现爷爷-父亲-儿子的关系,不然关系层数太多无法应用三代间的递归规律。 relation[a]=(relation[a]+relation[t])%3;//再回溯更新每一点与根节点的直接关系! return pre[a]; } void join(int a,int b,int d) { int fx=finded(a); int fy=finded(b); pre[fy]=fx; relation[fy]=(3-relation[b]+d-1+relation[a])%3;//矢量相加,更新根之间的关系! } int main() { scanf("%d%d",&N,&K); memset(relation,0,sizeof(relation));//刚开始每个节点与自己是同类 int sum=0;//记录假话 for(int i=1;i<=N;i++) pre[i]=i;//每个节点父亲是自己 while(K--) { int D,x,y; scanf("%d%d%d",&D,&x,&y); if((D==2&&x==y)||(x>N||y>N))//简单情况 sum++; else if(finded(x)==finded(y))//在一个关系网里面 { if(D==1&&relation[x]!=relation[y]) sum++; else if(D==2&&(relation[x]+1)%3!=relation[y]) sum++; } else//不在一个关系网里面 join(x,y,D);//要拉入关系网建立集合关系 } cout<<sum<<endl; return 0; }