Luogu P2024 [NOI2001]食物链

思路

一、种类并查集

我们发现对于任意两种动物A和B,他们之间的关系有同类、A吃B和A被B吃这三种可能。这样的话我们考虑对于每一个动物维护三个集合,分别是它的同类集合、它的猎物集合和它的天敌集合。

在考虑某句话是否是假话时,对于前两种假话(自己吃自己和超出范围)都非常好判断。那么对于第三种假话,我们分成两种情况来考虑。

1.当给出的这两个动物是同类时:

那么,这种情况下我们只需要判断A的猎物集合和A的天敌集合里有没有B即可。如果有,那么显然就是矛盾的,是假话。

如果不矛盾,那么就把A和B的天敌集合、同类集合以及猎物集合合并即可。

2.给出的这两个动物是吃与被吃的关系时:

对于这种情况,根据给出的顺序是A吃B,这样我们只需要判断一下A的天敌集合中是否有B和A的同类集合中是否有B即可。如果有,那显然矛盾,是假话。

如果不矛盾,那么就要把A的猎物集合和B的同类集合、B的天敌集合和A的同类集合以及A的天敌集合和B猎物集合合并即可(满足题目给出的循环关系)。

Code

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define MAXN 150050
int n, k, ans = 0;
int f[MAXN];//friend/be ate/eat
inline int get_father(int k){
    return f[k] == k ? k : f[k] = get_father(f[k]);
}
inline void merge1(int x,int y){
    f[get_father(x)] = get_father(y);
    f[get_father(x + n)] = get_father(y + n);
    f[get_father(x + 2 * n)] = get_father(y + 2 * n);
    return;
}//同类时的合并
inline void merge2(int x,int y){
    f[get_father(x + 2 * n)] = get_father(y);
    f[get_father(x + n)] = get_father(y + 2 * n);
    f[get_father(x)] = get_father(y + n);
    return;
}//x吃y时的合并
int main(){
    scanf("%d%d", &n, &k);
    for (int i = 1; i <= n * 3; ++i)
        f[i] = i;
    for (int i = 1; i <= k;++i){
        int opt, x, y;
        scanf("%d", &opt);
        scanf("%d%d", &x, &y);
        if(opt==2 && x==y){
            ++ans;
            continue;//弱智假话
        }
        if(x>n || y>n){
            ++ans;
            continue;//弱智假话
        }
        if(opt==1){
            if(get_father(x+n)==get_father(y)||get_father(x+2*n)==get_father(y))
                ++ans;//判断假话
            else merge1(x, y);//否则合并
        }
        if(opt==2){
            if(get_father(x)==get_father(y)||get_father(x+n)==get_father(y))
                ++ans;
            else merge2(x, y);//同上
        }
    }
    printf("%d\n", ans);
    return 0;
}

二、权值并查集

权值并查集做法与种类并查集比较类似,权值并查集做法只是用距离来表示两点间的关系(0为同类,1为猎物,2为天敌)。

关于假话,我们同样是分情况讨论一下。

1.当A和B是同类时:

首先考虑他们是否在同一集合内。若不在同一集合内,那么任何可能的描述都会是正确的。这样的话我们就需要将这两个集合合并。因为是同类,所以距离可以更新为:d[f[x]]=(d[y]-d[x]+3)%3(加三是

为了防止出现负数的情况,这个式子自己用样例推一下就能明白,这里我不提供分析思路了,大家可以独立思考一下)。

若在同一集合内,那么比较他们到根节点的距离是否相等。若不相等,那么就意味着他们与根节点的关系不同,与同类的说法矛盾,即为假话。

2.当A和B是吃与被吃的关系时:

同样,和上面那种情况一样,先判断是否在同一集合中,若不在,那么合并这两个集合,距离关系更新为d[f[x]]=(d[y]-d[x]+4)%3(这个还是找个例子推一下即可,不解释)。

那如果是在同一集合中,判断他们是否是吃与被吃的距离关系(具体会在代码中体现),若不符合,即为假话。

Code

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define MAXN 50010
int n, k, ans;
int f[MAXN], d[MAXN];
inline int get_father(int k){
    if(f[k]!=k){
        int re = f[k];
        f[k] = get_father(f[k]);//这里注意要先更新上面的的点与根节点的关系
        d[k] = (d[k] + d[re]) % 3;//更新与根节点的距离关系
    }
    return f[k];
}
inline void merge1(int x,int y){
    d[f[x]] = (d[y] - d[x] + 3) % 3;
    f[f[x]] = f[y];
    return;
}//同类时的合并
inline void merge2(int x,int y){
    d[f[x]] = (d[y] - d[x] + 4) % 3;
    f[f[x]] = f[y];
    return;
}//吃与被吃时的合并
int main(){
    scanf("%d%d", &n, &k);
    for (int i = 1; i <= n;++i)
        f[i] = i, d[i] = 0;
    for (int i = 1; i <= k;++i){
        int opt, x, y;
        scanf("%d", &opt);
        scanf("%d%d", &x, &y);
        if((opt==2&&x==y)||x>n||y>n){
            ++ans;
            continue;//弱智假话
        }
        if(opt==1){
            if(get_father(x)==get_father(y)&&d[x]!=d[y])
                ++ans;//若在同一集合,不满足位置关系的话,为假话
            else merge1(x, y);//否则合并
        }
        if(opt==2){
            if(get_father(x)==get_father(y)&&d[x]!=(d[y]+1)%3)
                ++ans;//同上,距离关系的判断自己手推例子就能明白
            else merge2(x, y);//否则合并
        }
    }
    printf("%d\n", ans);
    return 0;
}

posted @ 2020-07-26 16:36  Shadow_hyc  阅读(195)  评论(0)    收藏  举报