Luogu P2024 [NOI2001]食物链


思路
一、种类并查集
我们发现对于任意两种动物A和B,他们之间的关系有同类、A吃B和A被B吃这三种可能。这样的话我们考虑对于每一个动物维护三个集合,分别是它的同类集合、它的猎物集合和它的天敌集合。
在考虑某句话是否是假话时,对于前两种假话(自己吃自己和超出范围)都非常好判断。那么对于第三种假话,我们分成两种情况来考虑。
1.当给出的这两个动物是同类时:
那么,这种情况下我们只需要判断A的猎物集合和A的天敌集合里有没有B即可。如果有,那么显然就是矛盾的,是假话。
如果不矛盾,那么就把A和B的天敌集合、同类集合以及猎物集合合并即可。
2.给出的这两个动物是吃与被吃的关系时:
对于这种情况,根据给出的顺序是A吃B,这样我们只需要判断一下A的天敌集合中是否有B和A的同类集合中是否有B即可。如果有,那显然矛盾,是假话。
如果不矛盾,那么就要把A的猎物集合和B的同类集合、B的天敌集合和A的同类集合以及A的天敌集合和B猎物集合合并即可(满足题目给出的循环关系)。
Code
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define MAXN 150050
int n, k, ans = 0;
int f[MAXN];//friend/be ate/eat
inline int get_father(int k){
return f[k] == k ? k : f[k] = get_father(f[k]);
}
inline void merge1(int x,int y){
f[get_father(x)] = get_father(y);
f[get_father(x + n)] = get_father(y + n);
f[get_father(x + 2 * n)] = get_father(y + 2 * n);
return;
}//同类时的合并
inline void merge2(int x,int y){
f[get_father(x + 2 * n)] = get_father(y);
f[get_father(x + n)] = get_father(y + 2 * n);
f[get_father(x)] = get_father(y + n);
return;
}//x吃y时的合并
int main(){
scanf("%d%d", &n, &k);
for (int i = 1; i <= n * 3; ++i)
f[i] = i;
for (int i = 1; i <= k;++i){
int opt, x, y;
scanf("%d", &opt);
scanf("%d%d", &x, &y);
if(opt==2 && x==y){
++ans;
continue;//弱智假话
}
if(x>n || y>n){
++ans;
continue;//弱智假话
}
if(opt==1){
if(get_father(x+n)==get_father(y)||get_father(x+2*n)==get_father(y))
++ans;//判断假话
else merge1(x, y);//否则合并
}
if(opt==2){
if(get_father(x)==get_father(y)||get_father(x+n)==get_father(y))
++ans;
else merge2(x, y);//同上
}
}
printf("%d\n", ans);
return 0;
}
二、权值并查集
权值并查集做法与种类并查集比较类似,权值并查集做法只是用距离来表示两点间的关系(0为同类,1为猎物,2为天敌)。
关于假话,我们同样是分情况讨论一下。
1.当A和B是同类时:
首先考虑他们是否在同一集合内。若不在同一集合内,那么任何可能的描述都会是正确的。这样的话我们就需要将这两个集合合并。因为是同类,所以距离可以更新为:d[f[x]]=(d[y]-d[x]+3)%3(加三是
为了防止出现负数的情况,这个式子自己用样例推一下就能明白,这里我不提供分析思路了,大家可以独立思考一下)。
若在同一集合内,那么比较他们到根节点的距离是否相等。若不相等,那么就意味着他们与根节点的关系不同,与同类的说法矛盾,即为假话。
2.当A和B是吃与被吃的关系时:
同样,和上面那种情况一样,先判断是否在同一集合中,若不在,那么合并这两个集合,距离关系更新为d[f[x]]=(d[y]-d[x]+4)%3(这个还是找个例子推一下即可,不解释)。
那如果是在同一集合中,判断他们是否是吃与被吃的距离关系(具体会在代码中体现),若不符合,即为假话。
Code
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define MAXN 50010
int n, k, ans;
int f[MAXN], d[MAXN];
inline int get_father(int k){
if(f[k]!=k){
int re = f[k];
f[k] = get_father(f[k]);//这里注意要先更新上面的的点与根节点的关系
d[k] = (d[k] + d[re]) % 3;//更新与根节点的距离关系
}
return f[k];
}
inline void merge1(int x,int y){
d[f[x]] = (d[y] - d[x] + 3) % 3;
f[f[x]] = f[y];
return;
}//同类时的合并
inline void merge2(int x,int y){
d[f[x]] = (d[y] - d[x] + 4) % 3;
f[f[x]] = f[y];
return;
}//吃与被吃时的合并
int main(){
scanf("%d%d", &n, &k);
for (int i = 1; i <= n;++i)
f[i] = i, d[i] = 0;
for (int i = 1; i <= k;++i){
int opt, x, y;
scanf("%d", &opt);
scanf("%d%d", &x, &y);
if((opt==2&&x==y)||x>n||y>n){
++ans;
continue;//弱智假话
}
if(opt==1){
if(get_father(x)==get_father(y)&&d[x]!=d[y])
++ans;//若在同一集合,不满足位置关系的话,为假话
else merge1(x, y);//否则合并
}
if(opt==2){
if(get_father(x)==get_father(y)&&d[x]!=(d[y]+1)%3)
++ans;//同上,距离关系的判断自己手推例子就能明白
else merge2(x, y);//否则合并
}
}
printf("%d\n", ans);
return 0;
}

浙公网安备 33010602011771号