P2024 [NOI2001] 食物链
代码思路详解:
-
数据结构设计:
-
使用扩展的并查集,每个动物x维护三个信息:
-
x
:自身 -
x + n
:x的食物 -
x + 2n
:x的天敌
-
-
-
关系处理逻辑:
-
同类关系(op==1):
-
检查是否已经存在捕食或天敌关系
-
合并自身、食物和天敌三个维度的信息
-
-
捕食关系(op==2):
-
检查是否已经是同类或反向捕食关系
-
建立x吃y的关系
-
关键点:建立x的天敌和y的食物之间的关系,维护食物链循环
-
-
-
关键点解释:
-
merge(x + 2n, y + n)
保证了食物链的循环性:-
如果x吃y,那么x的天敌应该吃x
-
y的食物应该被x的天敌吃
-
这样才能形成"A吃B,B吃C,C吃A"的循环关系
-
-
-
假话判断:
-
所有违反上述关系约束的陈述都会被判定为假话
- 边界条件(范围检查、自己吃自己)也会被判定为假话
-
参考代码
#include<bits/stdc++.h> using namespace std; const int N = 5E4 + 10; int f[N * 3]; // 并查集数组:0~n-1表示自身,n~2n-1表示食物,2n~3n-1表示天敌 int n,k; // 并查集查找函数(带路径压缩) int find(int x) { if(f[x] != x) f[x] = find(f[x]); // 路径压缩优化 return f[x]; } // 并查集合并函数 void merge(int x, int y) { int fx = find(x), fy = find(y); f[fy] = fx; // 将y所在集合合并到x所在集合 } // 检查并处理当前关系的函数 bool check(int op,int x,int y) { // 边界条件检查:超出范围或自己吃自己 if(x > n || y > n || (x == y && op == 2)) return 0; if(op == 1) { // 处理同类关系 // 检查矛盾:x和y不能是捕食关系 if(find(x) == find(y + n) || find(y) == find(x + n)) return 0; // 检查矛盾:x和y不能是天敌关系 if(find(x) == find(y + 2 * n) || find(y) == find(x + 2 * n)) return 0; // 合并同类关系 merge(x, y); // 自身是同类 merge(x + n, y + n); // 食物相同 merge(x + 2 * n, y + 2 * n); // 天敌相同 } else { // 处理捕食关系:x吃y // 检查矛盾:x和y不能是同类或y吃x if(find(x) == find(y) || find(x) == find(y + n)) return 0; // 建立捕食关系 merge(x + n, y); // x的食物是y merge(y + 2 * n, x); // y的天敌是x /* 关键点解释:x的天敌是y的食物 这是为了保证食物链的循环性: - 如果x吃y,那么x的天敌应该吃x - 同时y的食物应该被x的天敌吃 - 这样才能形成A吃B,B吃C,C吃A的循环关系 */ merge(y + n, x + 2 * n); // x的天敌是y的食物 } return 1; // 关系合法 } int main() { cin >> n >> k; // 初始化并查集,每个元素自成一个集合 for(int i = 1; i <= 3 * n; i++) f[i] = i; int ans = 0; // 记录假话数量 for(int i = 1; i <= k; i++) { int op,x,y; cin >> op >> x >> y; if(!check(op,x,y)) ans++; // 如果关系不合法,假话数+1 } cout << ans; return 0; }