POJ - 1182
食物链
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),输出假话的总数。 |
用到 并查集的fa[]数组,指的是 当前点x的祖先,
而用一个path【】数组,表示 当前点x与祖先的距离, 如果距离%3==0,与祖先为同类,距离%3==1表示 祖先吃x,距离%3==2表示x吃祖先,
之所以模三 是因为 关系就三种,且 刚好是 连续、构成一个环的关系。 因为关系只有三种 所以 大于等于三的距离,与 其自身对三的取模值 是等价的
【所以一个fa根节点的集合内,他们之间所有节点的联系 是 x 与祖先的 关系】
首先肯定判断是否有 自己吃自己的情况,其次 x、y大于n的情况.
并且 如果 给出的x与y 根本不在一个集合内,那么无论怎么说,都是合法的(因为是第一次出现)
如果d==1,表示 认为x与y是同类
我们找到他们的根,fx,fy。
如果fx==fy; 那么他们是同一祖先,判断他们是否同类==》 判断他们与祖先的关系是否同样==》 他们与祖先的距离是否一样,即path[x]==path[y];
如果fx!=fy; 那么他们不是同祖先, 新给的关系肯定是合法的,我们只需要维护,也就是 把两个集合合并 (本代码以fy为新根), 并更新fx集合中所有点到 新根fy的距离,更新的过程如下:
首先 原来 y-->fy的距离为f[y] , x---->fx的距离为f[x],
合并之后的关系是 x---->fx------>fy<--------y;
因为x与y同类,所以distance(x,y)=0,
所以他们的【距离关系】可以近似看作这样的情况 y(x)----->fx---->fy;
所以得出 path[fx]=path[y]-path[x]; 前者指y到fy的距离,后者指,x到fx的距离;
如果d==2,表示x吃y
先找根
如果fx==y; 那么他们是同一祖先,判断是否满足x吃y ==》 判断他们与祖先关系只差是否为2 ,即((path[x]-path[y]-2)+3)%3==0 ;取模是怕出现负数 至于为什么用 差2 而不是差1 ,与我们每次选fy为根节点有关,见代码注释;
如果fx!=fy,则需要 维护,也就是合并集合, 并更新fx集合中所有点到 新根fy的距离,更新的过程同上:
首先 原来 y-->fy的距离为f[y] , x---->fx的距离为f[x],
合并之后的关系是 x---->fx------>fy<--------y;
因为x吃y,所以distance(x,y)=2, //ps:如果一直默认以fx为新根,dis会是1,想想为什么,和上面的判断是同一个道理
所以他们的【距离关系】可以近似看作这样的情况 x---->y----->fx---->fy;
所以得出 path[fx]=path[y]-path[x]+2; 前者指y到fy的距离,后者指,x到fx的距离;
所有path的计算都要注意如果得到负数或者大于等于3的结果要取模,本代码的实现放在里find函数中
#include <cstdio> #include <cmath> #include <cstring> #include <string> #include <algorithm> #include <iostream> #include <queue> #include <map> #include <set> #include <vector> using namespace std; int n; int path[50005]; int fa[50005]; int find(int x) { if (fa[x]==x) {path[x]=(path[x]+3)%3; return fa[x];} else { int fx=find(fa[x]); path[x]=(path[fa[x]]+path[x]+3)%3;// 更新为:自己到原先fa[x]的路径 + 新的fa[x]到根节点的路径 return fa[x]=fx; } } int main() { int n,k; cin>>n>>k; int op,x,y; int i ; for (i=1;i<=n;i++) fa[i]=i,path[i]=0; int cun=0; for (i=1;i<=k;i++) { scanf("%d%d%d",&op,&x,&y); if (x>n||y>n) {cun++;continue;} if (x==y&&op==2) {cun++;continue;} int fx=find(x); int fy=find(y); //找到各自的根 if (op==1) //同类判断 { if (fx==fy) //如果同根 { if (path[x]!=path[y]) //那么他们与祖先的距离应该相同, cun++; continue; } else if (fx!=fy) //如果不同类则合并 { fa[fx]=fy; //由fx指向fy; path[fx]=path[y]-path[x]; //由向量关系得 path[fx]=path[y]+dis(x-y)-path[x],其中dis(x-y)=0; //注意可能为负数,因此在find函数中加了+3%3操作,也可直接在此+3%3 } } else { //因为当x吃y,且要合并他们时,我们默认把fy作为新根,所以描述x吃y的关系用的是 path[x]=path[y]+2;//如果用fx为根,那么应该用path[x]=path[y]+1; if (fx==fy) //由于上一行的关系,所以此处判断是否符合x吃y的关系式 是path[x]-path[y]!=2 而不是差1 { if ((path[x]-path[y]+3)%3!=2) //这比较必定要+3%3 cun++; } else { fa[fx]=fy; path[fx]=path[y]+2-path[x]; //注意可能为负数,因此在find函数中加了+3%3操作,也可直接在此+3%3 } } } printf("%d\n",cun); return 0; }