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。
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 <cstdio>
#include <algorithm>
using namespace std;
const int N=50050;
int p[N];
//num数组 0表示和父节点同类 1表示被父节点吃,2表示吃父节点
//所有取模前提条件都是A吃B B吃C C吃A
int num[N];
int find(int x){
    if(x==p[x]){
        return x;
    }
    else{
        int fa=p[x];
        p[x]=find(p[x]);
        //一定要先递归压缩路径,再更新num!
        //第一次find的时候num[x]和num[p[x]]都是0
        //假如num[x]是1,表示被p[x]吃,而num[p[x]]是2,那么更新后num[x]就是0
        //即x吃父节点p[x],而p[x]吃p[p[x]],所以x和p[p[x]]同类,num[x]为0
        //同样道理,如果num[x]是2,而num[p[x]]是1,更新后num[x]也为0
        //如果num[x]和num[p[x]]同为1或者同为2,根据A吃B B吃C C吃A,取模3即可(巧妙)
        //这里一定要先保存这个fa,不能用递归更新后的p[a]
        num[x]=(num[x]+num[fa])%3;
        return p[x];
    }
}
bool join(int a,int b,int q){
    int fa = find(a);
    int fb = find(b);
    //同一个根节点
    if(fa == fb){
        //q是操作 1表示同类 2表示a吃b
        //q如果是1 那么a和b就是同类,那么num值不可能出现一个1一个2的情况
        //q如果是2 那么a吃b,那么为了使得满足三角关系,b必须吃父节点,也就是num[b]为2,num[a]为1
        //或者num[b]为0,num[a]为2,即a吃b,a吃fa/fb,b和父节点同类
        //    .....(fa/fb).....
        //  a---------------------b
        if(num[b] != (num[a] + (q - 1)) % 3){
            return true;
        }
        else{
            return false;
        }
    }
    else{
        //合并
        p[fb] = fa;
        //q是1的情况,a,b同类,num[fb]为0
        //q是2的情况,a吃b,有几种合并的情况
        //第一种:a本身是根节点,b有父节点,这样a吃b,合并后其实是p[fb]=a,num[fb]要分情况,
        //如果b是被fb吃,那么a和fb就应该是同类,所以num[b]=1 num[a]=0 得到num[fb]=0
        //第二种:a有父节点,b本身是根节点,这样a b fa就成了一个三角,a吃b,所以
        //如果a被fa吃,那么b一定要吃fa,更新前num[b]是0(本身是根节点),
        //num[a]是1 得到num[fb]也就是num[b]为2 符合b吃fa
        num[fb] = (3-num[b]+num[a]+(q-1))%3;
        //没有关系连通,所以肯定不会矛盾
        return false;
    }
}
int main(void){
    //freopen("data.txt","r",stdin);
    int n,k;
    scanf("%d%d",&n,&k);
    for(int i=0;i<n;i++){
        p[i]=i;
    }
    int q,a,b;
    int cnt=0;
    while(k--){
        scanf("%d%d%d",&q,&a,&b);
        if(a>n || b>n ||(q==2 && a==b)){
            cnt++;
        }
        else{
            if(join(a,b,q)){
                cnt++;
            }
        }
    }
    printf("%d\n",cnt);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/westbrook1998/article/details/81750214