扩展域并查集
扩展域并查集就是指:将并查集的状态由基本的:几个元素属于朋友,则放入并查集;
扩展为更多状态。
更多通过题目来理解把。
P2024 [NOI2001] 食物链
题意
动物王国中有三类动物 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 句话有的是真的,有的是假的。
当一句话满足下列三条之一时,这句话就是假话,否则就是真话。
当前的话与前面的某些真的话冲突,就是假话;
当前的话中 X 或 Y 比 N 大,就是假话;
当前的话表示 X 吃 X,就是假话。
你的任务是根据给定的 N 和 K 句话,输出假话的总数。
思路
可以将状态分为:
同类,食物,天敌
也就是说,将状态扩展为:
-
同类:如果a和b是同一类,则将a和b合并到一个并查集
-
食物:如果a是b的食物,则将a和b合并到一个并查集
-
天敌:如果a是b的天敌,则将a和b合并到一个并查集
将状态扩展后,相应的合并也要修改。
-
如果a和b是同类,那么
a的天敌、同类、食物也是b的天敌、同类、食物。 -
如果a是b的天敌,那么
a的天敌是b的食物;
a的同类是b的天敌;
a的食物是b的同类。
然后再判断题目的几个条件
当前的话中 X 或 Y 比 N 大,就是假话;
直接判断即可
当前的话表示 X 吃 X,就是假话。
当输入为同类时,直接判断即可
当前的话与前面的某些真的话冲突,就是假话;
如果输入a是b的同类,那么需要确定以下条件,才能算对
- a的天敌没有b
- a的食物没有b
不需要判断:a的同类是不是b的同类
如果输入a是b的天敌,那么需要确定以下条件,才能算对
- a的同类没有b
- a的天敌没有b
不需要判断:a的食物是不是b的同类
实现
就是用3倍的并查积的存各种动物的关系:一倍存本身,二倍存食物,三倍存天敌。
每次维护三个并查积的关系就可以了
具体看代码吧
代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define pii pair<int, int>
const int N =1e5 + 10;
int fa[N*3];
int n,m;
int find(int x){
if(fa[x]!=x) fa[x]=find(fa[x]);
return fa[x];
}
void slove()
{
cin>>n>>m;
for(int i=1;i<=3*n;++i) fa[i]=i;
int ans=0;
int x1, x2, x3, y1, y2, y3;
for(int i=1;i<=m;i++){
int a,x,y;cin>>a>>x>>y;
if(x>n || y>n){
ans++;
continue;
}
x1 = find(x);//同类
x2 = find(x + n);//食物
x3 = find(x + n * 2);//天敌
y1 = find(y);
y2 = find(y + n);
y3 = find(y + n * 2);
if(a==1){
//如果1是2的天敌或猎物,显然为谎言
if(x2==y1 || x3==y1){
ans++;
continue;
}
fa[x1]=y1 , fa[x2]=y2 , fa[x3]=y3;
}
else{
//如果1是2的同类或猎物,显然为谎言
if(x1==y1 || x3==y1){
ans++;
continue;
}
fa[x2] = y1 , fa[x3]=y2 , fa[x1]=y3;
}
}
cout<<ans<<endl;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t = 1;
// cin >> t;
while (t--)
slove();
return 0;
}