POJ 1182 食物链
题目链接:http://poj.org/problem?id=1182
这题的难点是如何确定每个动物的类别,因为光知道 a吃b,b吃c,a有可能为A、B、C中的任意一种,而且一旦说明了某两种是同类,则于这两种相关的动物的类别都需要修改,很是麻烦。
所以,好的方法不是先确定动物的类别,而是确定两个动物之间的关系。
1.用集合表示集合内的某两个动物能确定某种关系,0表示同类,1表示吃,2表示被吃。
2.集合应该能合并,如果两个动物分别处于不同的集合,当确定关于这两个动物的关系时,这两个动物就应属于同个集合(根据集合声明的意义)。所以使用并查集合并集合。
并查集是个树结构,每个节点设置一个关系标志位,记录表示当前节点相对于其父母节点的关系(0、1、2)。路径压缩关系时更新为与树根的关系(直接连接根节点)。当更新一个节点的关系标志时,其父母节点由于路径压缩的原因一定被直接连接到根节点。所以可以根据当前节点与父母节点的关系和父母节点与根节点的关系推算出当前节点的关系,并直接连接到根节点。利用路径压缩递归的回溯,对于每个节点都存在其父母连接根节点的情况发生。所以只需考察这种情况如何推算就行了。
假设a->b (a的父母是b,下同), b->root ; a于b的关系是 w[a],b于root的关系是 w[b]。可列表格表示推算的结果:
推算结果 | w[b] | |||
0 | 1 | 2 | ||
w[a] | 0 | 0 | 1 | 2 |
1 | 1 | 2 | 0 | |
2 | 2 | 0 |
1 |
得出的结论是 w[a]=(w[a]+w[b])%3
对于判断矛盾。如果这两个动物不是在一个集合里(不存在任何关系),则肯定不矛盾,合并集合并更新。否则就根据W来判断。
这题很巧妙的地方就是利用路径压缩构造出同一种情况,根据这种情况可以简单判断出每个节点与树根的关系。最后如果要确定每个动物的种类,只需要假设唯一一个集合的根的种类,剩下的依据与根的关系就可推出。所以最后有3种情况。
1 #include <stdio.h> 2 #include <string.h> 3 const int maxn=50002; 4 int p[maxn],w[maxn]; 5 int dir[2][3]= {{0,2,1},{1,0,2}}; 6 inline int updata(int x ,int y) 7 { 8 int ret=(x+y)%3; 9 return ret; 10 } 11 void init(int N) 12 { 13 memset(w,0,sizeof w); 14 for(int i=0 ; i<=N ; i++) 15 { 16 p[i] = i; 17 } 18 } 19 void uion(int x, int y , int val) 20 { 21 p[x] = y; 22 w[x] = val; 23 } 24 int Find(int x) 25 { 26 if( p[x] != x) 27 { 28 Find(p[x]); 29 w[x] = updata(w[x],w[p[x]]); 30 p[x] = Find(p[x]); 31 } 32 return p[x]; 33 } 34 int main() 35 { 36 int N , K; 37 int kind,tx,ty,ans=0; 38 scanf("%d %d",&N,&K); 39 init(N); 40 while( K-- ) 41 { 42 scanf("%d%d%d",&kind,&tx,&ty); 43 if( tx>N || ty > N || (kind==2&&tx==ty) ) 44 { 45 ans++; 46 continue; 47 } 48 if(Find(tx)!=Find(ty)) 49 { 50 int fx=Find(tx); 51 uion(fx,ty,dir[kind-1][w[tx]]); 52 Find(tx); 53 } 54 else 55 { 56 if( kind == 1) 57 { 58 if( w[tx]!=w[ty]) ++ans; 59 } 60 else 61 { 62 if((w[tx]==1&&w[ty]==0)||(w[tx]==2&&w[ty]==1)||(w[tx]==0&&w[ty]==2)) 63 continue; 64 ++ans; 65 } 66 } 67 } 68 printf("%d\n",ans); 69 return 0; 70 }