POJ1182
https://vjudge.net/problem/POJ-1182
现有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
以下K行每行是三个正整数 D,X,Y,两数之间用一个空格隔开,其中D表示说法的种类。
若D=1,则表示X和Y是同类。
若D=2,则表示X吃Y。
Output
Sample Input
100 7 1 101 1 2 1 2 2 2 3 2 3 3 1 1 3 2 3 1 1 5 5
Sample Output
3
#include<iostream> #include<stdio.h> #include<string.h> #include<algorithm> #define maxn 50010 using namespace std; struct node { int pre; int relation; }p[maxn]; int Find(int x) { int temp; if(x==p[x].pre) return x; temp=p[x].pre; p[x].pre=Find(temp); p[x].relation=(p[x].relation+p[temp].relation)%3; return p[x].pre; } int main() { int n,k; int ope,a,b; int root1,root2; int sum=0; scanf("%d%d",&n,&k); for(int i=1;i<=n;i++) { p[i].pre=i; p[i].relation=0; } for(int i=1;i<=k;i++) { scanf("%d%d%d",&ope,&a,&b); if(a>n||b>n) { sum++; continue; } if(ope==2&&a==b) { sum++; continue; } root1=Find(a); root2=Find(b); if(root1!=root2) { p[root2].pre=root1; p[root2].relation=(p[a].relation+(ope-1)+3-p[b].relation)%3; } else { if(ope==1&&p[a].relation!=p[b].relation) { sum++; continue; } if(ope==2&&(3-p[a].relation+p[b].relation)%3!=ope-1) { sum++; continue; } } } cout<<sum<<endl; return 0; }
思路:
//这道题花了我一天才想通,菜是原罪(ಥ _ ಥ)!
1.题中:第一种说法是"1 X Y";第二种说法是"2 X Y"。可以假设变量ope,当的值为 1,表示X和Y是同类;当值为2,表示X吃Y。
题中给了关于X和Y的两种状态,而实际情况中有三种状态需要表示:假设操作变量m,当m值为 0 代表X,Y是同类,1 代表X吃Y,2 代表X被Y吃。
(这里假设X为根节点,m为根节点X到节点Y的操作,特别注意操作的方向,可以把两点的操作关系理解为向量)
那么怎么用假设表示题中给的初始量?我们引入偏移量1,将题中的量减1,可以得到假设的量。即m=ope-1;
2.构建结构体,包含前驱节点和 relation(根节点到该节点的关系)相当于我们上面说的m;
3.Find()不仅要找根节点(在设节点初值的时候,每个节点都是指向自己的。不断找父节点直到找到一个节点它指向自己,说明它就是根节点。),还要更新操作域的等式p[x].relation=(p[x].relation+p[temp].relation)%3;。
举个栗子来形象化理解一下,(假设树长这样W->X->Y->Z),在这里Find()先查找根节点的时候,(根据前面的假设是父亲节点指向子节点),即找到Z,
Z.relation=(Z.relation+Z.pre.relation)%3=0; Z->Z
再根据递归 Y.relation=(Y.relation+Z.relation)%3; Z->Y
再根据递归 X.relation=(X.relation+Y.relation)%3; Z->X(Z->Y->X) 此时Y的relation已更新
再根据递归W.relation=(W.relation+X.relation)%3; W.relation代表X->W,初始X.relation代表Y->X,此时已更新代表Z->X;
可将此类比为向量,Z->W=(Z->X+X->W)%3;
(Z->X+X->W)可以大于等于3,为了保证0<=relation<=2,所以要将(Z->X+X->W)的值 %3。
4.判断假话
2) 当前的话中X或Y比N大,就是假话;
3) 当前的话表示X吃X,就是假话。
//2)3)的判断比较简单,用if条件判断即可。
1) 当前的话与前面的某些真的话冲突,就是假话;
//如果X,Y非同根,将它们合并为同根。根据一开始分析的假设,我们将X的根节点作为Y根节点的父节点。
操作域怎么更新?
p[root2].relation=(p[x].relation+(ope-1)+3-p[y].relation)%3;//root2代表Y的根节点,即X,root1代表X的根节点;m=ope-1;
p[x].relation :root1->X
p[y].relation :root2->Y 3-p[y].relation :Y->root2//相对位置发生改变,此时是以Y为根节点来看;
ope-1 :X->Y
root1->root2=(root1->X+X->Y+Y->root2)%3;
//如果X,Y同根,
ope==1,X,Y同类,如果X与Y的操作数不同则为假话
ope==2,X吃Y,需要验证X->Y是否与ope-1相同
X->Y=X->root1+root1->Y= X->root1+root2->Y(因为X,Y同根)=(3-p[x].relation)+p[y].relation。