[lnsyoj110/luoguP2024]食物链

题意

原题链接
三类元素 \(a,b,c\) 满足 \(a \to b\)\(b \to c\)\(c \to a\)。现在共有 \(n\) 个元素,给出 \(m\) 条关系 \(x \to y\)\(x\)\(y\) 种类相同,输出非法或与前面所属关系相矛盾的关系数量

sol

并查集可以处理“朋友的朋友是朋友”这样的传递关系,却不能处理“敌人的敌人是朋友”这样的传递关系。因此,我们需要使用种类并查集(两种元素的种类并查集又被称为 friend-enemy 并查集)。种类并查集本质是并查集的拓展应用,因此基本操作与并查集相同
对于本题,需要先设计种类。由于每类元素都可能出现同类、\(\to\)\(\gets\)三种情况,因此 1 为同类,2\(\gets\)3\(\to\)
注意:种类并查集中的种类是相对于两元素间的关系而言,而非元素的种类

查询两元素关系

显然,如果两个元素不在一个并查集中,它们之间也就没有关系。反之,如果两个元素在一个并查集中,就说明两者之间的关系即为两者在并查集中的种类间的关系。
因此,要判断同类,只需要判断 \(find(x) = find(y)\) 即可,另外两种关系同理

连接元素

(连接元素前需要先查询关系来判断矛盾和非法的关系)
若两元素是同类,由于不知道这两个元素是什么种类,因此需要在每个种类中都将两元素连接;若两元素关系为 \(x \to y\),与上同理,将三个种类都进行连接

代码

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 150005;

int fa[N];
int n, m;

int find(int x){
    if (x == fa[x]) return x;
    else return fa[x] = find(fa[x]);
}

void merge(int x, int y){
    int fx = find(x), fy = find(y);
    if (fx == fy) return ;
    fa[fy] = fx;
}

int main(){
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= 3 * n; i ++ ) fa[i] = i;
    int cnt = 0;
    while (m -- ){
        int op, x, y;
        scanf("%d%d%d", &op, &x, &y);
        if (x > n || y > n || x == y && op == 2) {
            cnt ++ ;
            continue;
        }
        if (op == 1){
            if (find(x + n) == find(y) || find(x + 2 * n) == find(y)){
                cnt ++ ;
                continue;
            }
            merge(x, y), merge(x + n, y + n), merge(x + 2 * n, y + 2 * n);
        }
        else {
            if (find(x) == find(y) || find(x + n) == find(y)){
                cnt ++ ;
                continue;
            }
            merge(x, y + n), merge(x + n, y + 2 * n), merge(x + 2 * n, y);
        }
    }

    printf("%d\n", cnt);

    return 0;
}
posted @ 2024-07-20 09:28  是一只小蒟蒻呀  阅读(4)  评论(0编辑  收藏  举报