【洛谷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 关押罪犯 扩展域并查集+贪心

浙公网安备 33010602011771号