题解:AcWing 240 食物链
【题目来源】
AcWing:240 食物链 - AcWing题库
【题目描述】
动物王国中有三类动物 \(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\),以一个空格分隔。
以下\(K\)行每行是三个正整数\(D,X,Y\),两数之间用一个空格隔开,其中\(D\)表示说法的种类。
若\(D=1\),则表示\(X\)和\(Y\)是同类。
若\(D=2\),则表示\(X\)吃\(Y\)。
【输出】
只有一个整数,表示假话的数目。
【输入样例】
100 7
1 101 1
2 1 2
2 2 3
2 3 3
1 1 3
2 3 1
1 5 5
【输出样例】
3
【核心思想】
-
问题分析:给定 \(N\) 个动物和 \(K\) 条食物链关系描述,需要判断每条描述的真假。动物分为 \(A, B, C\) 三类,形成环形食物链(\(A\) 吃 \(B\),\(B\) 吃 \(C\),\(C\) 吃 \(A\))。每条描述要么声明两个动物同类,要么声明一个吃另一个。假话判定条件包括:与前面真话冲突、编号越界、\(X\) 吃 \(X\)。关键观察是:可以用带权并查集维护每个动物与根节点的相对关系(模 \(3\) 意义下的距离)。
-
算法选择:
- 带权并查集(Disjoint Set Union with Weight):用 \(d[x]\) 表示 \(x\) 到其父节点的距离,模 \(3\) 后表示 \(x\) 相对于根节点的种类(\(0\):同类,\(1\):被根吃,\(2\):吃根)
- 路径压缩 + 距离更新:查找时递归更新每个节点到根节点的距离
-
关键步骤:
- 初始化:\(p[i] = i\),\(d[i] = 0\)(每个动物自成一个集合,距离为 \(0\))
- 查找
find(x):- 若 \(p[x] \neq x\),递归找到根 \(u = \text{find}(p[x])\)
- \(d[x] += d[p[x]]\)(累加父节点到根的距离)
- \(p[x] = u\)(路径压缩)
- 返回 \(u\)
- 处理操作:
- 假话判定:
- 若 \(x > N\) 或 \(y > N\):
res++ - 若 \(t = 2\) 且 \(x = y\):
res++(\(X\) 吃 \(X\))
- 若 \(x > N\) 或 \(y > N\):
- 同类操作 \(t = 1\)(\(X\) 和 \(Y\) 同类):
- 若 \(\text{find}(x) = \text{find}(y)\) 且 \((d[x] - d[y]) \bmod 3 \neq 0\):
res++(在同一集合但种类不同) - 若不在同一集合:合并,\(p[px] = py\),\(d[px] = d[y] - d[x]\)(使 \(d[x] \equiv d[y] \pmod 3\))
- 若 \(\text{find}(x) = \text{find}(y)\) 且 \((d[x] - d[y]) \bmod 3 \neq 0\):
- 捕食操作 \(t = 2\)(\(X\) 吃 \(Y\)):
- 若 \(\text{find}(x) = \text{find}(y)\) 且 \((d[x] - d[y] - 1) \bmod 3 \neq 0\):`res++$(在同一集合但 \(X\) 不吃 \(Y\))
- 若不在同一集合:合并,\(p[px] = py\),\(d[px] = d[y] + 1 - d[x]\)(使 \(d[x] - d[y] \equiv 1 \pmod 3\),即 \(X\) 比 \(Y\) 高一级)
- 假话判定:
-
时间/空间复杂度:
- 时间复杂度:\(O(K \cdot \alpha(N))\),其中 \(\alpha\) 为阿克曼函数的反函数,近似常数
- 空间复杂度:\(O(N)\),并查集数组
-
带权并查集的核心思想:
- 相对关系编码:用 \(d[x] \bmod 3\) 表示 \(x\) 相对于根节点的种类:\(0\) 表示同类,\(1\) 表示被根吃(根吃 \(x\)),\(2\) 表示吃根(\(x\) 吃根)。这种编码天然满足环形食物链的传递性
- 路径压缩的距离累加:
find时 \(d[x] += d[p[x]]\) 确保路径压缩后 \(d[x]\) 仍表示 \(x\) 到新根的距离,维护了权值的正确性 - 合并时的权值推导:合并两个集合时,通过代数推导确保新连接的权值满足给定的关系约束。例如同类操作要求 \(d[x] \equiv d[y] \pmod 3\),因此 \(d[px] = d[y] - d[x]\)
- 模运算的一致性:所有距离关系在模 \(3\) 意义下成立,利用 \((a - b) \bmod 3\) 判断两个节点是否同类、捕食或被捕食关系
- 适用于带相对关系的动态连通性问题、种类并查集、模意义下的等价类维护等场景
【解题思路】

【算法标签】
并查集
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 50010; // 定义最大节点数
int n, m; // n: 节点数量, m: 操作次数
int p[N]; // 并查集父节点数组
int d[N]; // 存储节点到父节点的距离(带权)
// 查找x的根节点(带路径压缩和距离更新)
int find(int x)
{
if (p[x] != x)
{
int u = find(p[x]); // 递归找到根节点
d[x] += d[p[x]]; // 更新当前节点到根节点的距离
p[x] = u; // 路径压缩
}
return p[x];
}
int main()
{
scanf("%d%d", &n, &m); // 输入节点数和操作数
// 初始化并查集
for (int i = 1; i <= n; i++)
p[i] = i;
int res = 0; // 记录假话数量
while (m--) // 处理每个操作
{
int t, x, y;
scanf("%d%d%d", &t, &x, &y);
// 检查节点编号是否合法
if (x > n || y > n)
{
res++; // 节点编号越界即为假话
}
else
{
int px = find(x), py = find(y);
if (t == 1) // 操作1:x和y是同类
{
// 如果已在同一集合但距离差不是3的倍数(不是同类)
if (px == py && (d[x] - d[y]) % 3)
res++;
else if (px != py) // 不同集合则合并
{
p[px] = py;
d[px] = d[y] - d[x]; // 维护距离关系
}
}
else // 操作2:x吃y
{
// 如果已在同一集合但距离差不满足x吃y的关系
if (px == py && (d[x] - d[y] - 1) % 3)
res++;
else if (px != py) // 不同集合则合并
{
p[px] = py;
d[px] = d[y] + 1 - d[x]; // 维护距离关系
}
}
}
}
printf("%d\n", res); // 输出假话总数
return 0;
}
【运行结果】
100 7
1 101 1
2 1 2
2 2 3
2 3 3
1 1 3
2 3 1
1 5 5
3
浙公网安备 33010602011771号