poj 1182 并查集
题意:三种动物,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,就是假话。
输出假话的总数。
分析:
1.p[x]表示x的根结点。r[x]表示p[x]与x的关系。r[x] == 0 表示p[x]与x同类;1表示p[x]吃x;2表示x吃p[x]。
2.集合不是根据x与p[x]是否是同类来划分,而是根据能否确定x与p[x]的关系来划分。
3.若 D == 1 而 r[X] != r[Y] 则此话为假。
若 D == 2 而 r[X] == r[Y] 或者 r[X] == ( r[Y] + 1 ) % 3 (Y吃X )则此话为假。
假设Y吃X: r[X] = 0 && r[Y] = 2
r[X] = 1 && r[Y] = 0
r[X] = 2 && r[Y] = 1
可知 r[X] = ( r[Y] + 1 ) % 3;
4.在union_set( rx , ry )过程中若将S(ry)合并到S(rx)上,则相应的r[ry]必须更新为ry相对于rx的关系。已知关系: rx与x, ry与y, x与y,易得rx与ry的关系。
const int MAXSIZE = 50005; int rank[MAXSIZE]; // 节点高度的上界 int parent[MAXSIZE]; // 根节点 int r[MAXSIZE]; int FindSet(int x){// 查找+递归的路径压缩 int px=parent[x]; if(x!=parent[x]) parent[x]=FindSet(parent[x]); r[x]=(r[px]+r[x])%3;//r[x]存放的是x对于根的偏移量 这里的 根 只能是px return parent[x]; } void UnionSet(int x,int y,int rx,int ry,int d){ if(rx==ry) return; /*parent[ry] = rx; r[ry] = ( r[x] - r[y] + 2 + d ) % 3;*/ if(rank[rx]>rank[ry]){ parent[ry]=rx; r[ry]=(r[x]-r[y]+2+d)%3; } else{ parent[rx]=ry; r[rx]=(r[y]-r[x]-d+4)%3; if(rank[rx]==rank[ry])rank[ry]++; } } void MakeSet(int SIZE){ for(int i=0;i<=SIZE;i++) { parent[i]=i; r[i]=rank[i]=0; } } int main() { int n,k; scanf("%d%d",&n,&k); MakeSet(n); int d,x,y,cnt=0; int rx,ry; while(k--){ scanf("%d%d%d",&d,&x,&y); rx=FindSet(x); ry=FindSet(y); if(d==1){ if(x>n||y>n)cnt++; else if(rx==ry&&r[x]!=r[y])cnt++; else UnionSet(x,y,rx,ry,d); } else{ if(x>n||y>n||x==y)cnt++; else if(rx==ry&&r[x]!=(r[y]+2)%3)cnt++; else UnionSet(x,y,rx,ry,d); } } cout<<cnt<<endl; return 0; }