【洛谷P2024】食物链[NOI2001]

学习第二种并查集!

下面我们介绍第二种并查集:“扩展域”并查集
听上去有点玄乎 但是可以从别的角度出发
我们平时做的都是单个集合的并查集
在这里 我们会把每个变量的每个性质剖离作为另外的n个变量
下面来看题目

P2024 [NOI2001] 食物链

题目描述

动物王国中有三类动物 \(A,B,C\),这三类动物的食物链构成了有趣的环形。\(A\)\(B\)\(B\)\(C\)\(C\)\(A\)

现有 \(N\) 个动物,以 \(1 \sim N\) 编号。每个动物都是 \(A,B,C\) 中的一种,但是我们并不知道它到底是哪一种。

有人用两种说法对这 \(N\) 个动物所构成的食物链关系进行描述:

  • 第一种说法是 1 X Y,表示 \(X\)\(Y\) 是同类。
  • 第二种说法是2 X Y,表示 \(X\)\(Y\)

此人对 \(N\) 个动物,用上述两种说法,一句接一句地说出 \(K\) 句话,这 \(K\) 句话有的是真的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。

  • 当前的话与前面的某些真的话冲突,就是假话;
  • 当前的话中 \(X\)\(Y\)\(N\) 大,就是假话;
  • 当前的话表示 \(X\)\(X\),就是假话。

你的任务是根据给定的 \(N\)\(K\) 句话,输出假话的总数。

输入格式

第一行两个整数,\(N,K\),表示有 \(N\) 个动物,\(K\) 句话。

第二行开始每行一句话(按照题目要求,见样例)

输出格式

一行,一个整数,表示假话的总数。

输入输出样例 #1

输入 #1

100 7
1 101 1
2 1 2
2 2 3
2 3 3
1 1 3
2 3 1
1 5 5

输出 #1

3

说明/提示

对于全部数据,\(1\le N\le 5 \times 10^4\)\(1\le K \le 10^5\)

解法&&个人感想

开头说的还是太抽象了
这样 我们把这题的每个x剖分成三个部分
x_self,x_eat,x_enemy(即x的自身,x的食物以及x的天敌)
那么对于指令1表示的情况,如果是假话,就有可能是以下情况:
1.x吃y,即x_eat和y_self在一个集合里
2.y吃x,即x_enemy和y_self在一个集合里
如果是真话,那么将x_self和y_self,x_eat和y_eat,x_enemy和y_enemy分别合并
对于指令2表示的情况,如果是假话,有可能是以下情况:
1.x和y是同一营养级,即x_self和y_self在一个集合里
2.y吃x,和上文一样
如果是真话,那么将x_eat和y_self,x_self和y_enemy,x_enemy和y_eat分别合并
下面是代码实现:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n,k;
struct node{
    int ans,l,r;
};
node q[100005];
int fa[300005];//规定self是x,x+n是eat,x+2n是enemy
int sum,x,y;
int get(int x){
    if(fa[x]==x) return x;
    return fa[x]=get(fa[x]);
}
void merge(int x,int y){
    fa[get(x)]=get(y);
    return ;
}
int main(){
    scanf("%d%d",&n,&k);
    for(int i=1;i<=3*n;i++) fa[i]=i;
    for(int i=1;i<=k;i++){
        scanf("%d%d%d",&q[i].ans,&q[i].l,&q[i].r);
        int l_self=q[i].l,r_self=q[i].r;
        int l_eat=q[i].l+n,r_eat=q[i].r+n;
        int l_enemy=q[i].l+2*n,r_enemy=q[i].r+2*n;
        if((q[i].ans==2&&q[i].l==q[i].r)||q[i].l>n||q[i].r>n){
            sum++;
            continue;
        }
        if(q[i].ans==1){
            if(get(l_eat)==get(r_self)||get(l_self)==get(r_eat)){
                sum++;
                continue;
            }
            else{
                merge(l_eat,r_eat);
                merge(l_self,r_self);
                merge(l_enemy,r_enemy);
            }
        }
        if(q[i].ans==2){
            if(get(l_self)==get(r_self)||get(l_self)==get(r_eat)){
                sum++;
                continue;
            }
            else{
                merge(l_self,r_enemy);
                merge(l_eat,r_self);
                merge(l_enemy,r_eat);
            }
        }
    }
    printf("%d",sum);
    system("pause");
    return 0;
}

在这之后,如果你想练习“扩展域”的并查集,我还推荐以下题目:
1.P5937 Parity Game 扩展域并查集+前缀和+离散化
2.P1892 团伙 扩展域并查集+计数(注意计数的时候n的取值)
3.P1525 关押罪犯 扩展域并查集+贪心

posted @ 2025-02-12 21:18  elainafan  阅读(104)  评论(0)    收藏  举报