[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;
}