洛谷 P2024 [NOI2001] 食物链
1 洛谷P2024 [NOI2001] 食物链
2 题目描述
时间限制 \(1s\) | 空间限制 \(125M\)
动物王国中有三类动物 \(A,B,C\), 这三类动物的食物链构成了有趣的环形。\(A\) 吃 \(B, B\) 吃 \(C, C\) 吃 \(A\)。
现有 \(N\) 个动物,从 \(1\) 到 \(N\) 编号。每个动物都是 \(A, B, C\) 中的一种,但是我们并不知道它到底是哪一种。
有人用两种说法对这 \(N\) 个动物所构成的食物链关系进行描述:
- 第一种说法是 \(1 \space X \space Y\),表示 \(X\) 和 \(Y\) 是同类。
- 第二种说法是 \(2 \space X \space Y\),表示 \(X\) 吃 \(Y\) 。
此人对 \(N\) 个动物,用上述两种说法,一句接一句地说出 \(K\) 句话,这 \(K\) 句话有的是真的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。
- 当前的话与前面的某些真的话冲突,就是假话
- 当前的话中 \(X\) 或 \(Y\) 比 \(N\) 大,就是假话
- 当前的话表示 \(X\) 吃 \(X\),就是假话
你的任务是根据给定的 \(N\) 和 \(K\) 句话,输出假话的总数。
数据范围:
\(1 ≤ N ≤ 5 \times 10^4\); \(1 ≤ K ≤ 10^5\)
3 题解
假话的判定中第二条和第三条十分简单,只需要简单模拟即可。
我们主要关注与之前的真话冲突这一条判定。我们考虑一句话如何才能不与之前的话冲突:
如果这句话说的是 \(X\) 吃 \(Y\),那么 \(X\) 一定不和 \(Y\) 为同类,\(Y\) 一定不会吃 \(X\),并且不存在一个 \(Z\) 使得 \(Z\) 吃 \(Y\) 且 \(X\) 吃 \(Z\)(原因请读者自行思考)。
如果这句话说的是 \(X\) 和 \(Y\) 为同类,那么 \(X\) 一定不会吃 \(Y\),\(Y\) 也一定不会吃 \(X\)。
我们这时发现,\(X\) 和 \(Y\) 是否为同类和谁吃谁的信息有点像连通性的问题,于是我们可以考虑使用并查集维护。
这个时候问题来了:是否是同类可以轻松用并查集维护,但是谁吃了谁就不好维护了。就算再开一个并查集把吃别的生物与被吃的生物都联通起来,也没法知道到底是谁吃谁。
这个时候,我们换一个思路,不从并查集本身进行改变,而是去改变元素本身。这个想法就叫扩展域并查集。
具体地来说,我们将每一个生物都拆分成三个元素:\(X_A\)(自己的物种),\(X_B\)(被自己吃的物种),\(X_C\)(吃自己的物种)。
这个时候,我们将 \(X_A\) 与 \(Y_B\) 连接到同一集合中,就代表了 \(X\) 吃 \(Y\)。
因此,我们判断 \(X\) 吃 \(Y\) 是否是假话的依据就变成了:
\(X_A \ne Y_A, X_C \ne Y_A, Y_C \ne X_B\)。
判断 \(X\) 和 \(Y\) 是同类是否是假话的依据就变成了:
\(X_B \ne Y_A, X_C \ne Y_A\)。
(这里的不等号指的是不在同一集合内,下文中的等号指的是在同一集合内)
这里注意,如果 \(X\) 吃 \(Y\) 成立,那么令 \(X_B = Y_A, Y_C = X_A, Y_B = X_C\)。如果 \(X\) 和 \(Y\) 是同类成立,那么令 \(X_A = Y_A, X_B = Y_B, X_C = Y_C\)。
我们可以用 \(X\) 表示 \(X_A\),用 \(X + N\) 表示 \(X_B\),用 \(X + 2\times N\) 表示 \(X_C\)。
4 代码(空格警告):
#include <iostream>
#include <cstdio>
using namespace std;
const int N = 5e4+10, M = 1e5+10;
int n, k, opt, x, y, cnt, xa, xb, xc, ya, yb, yc;
int fa[N * 3];
int find(int x)
{
if (fa[x] == x) return x;
return fa[x] = find(fa[x]);
}
int read()
{
int x = 0, f = 1;
char c = getchar();
while (c < '0' || c > '9')
{
if (c == '-') f = -1;
c = getchar();
}
while (c >= '0' && c <= '9')
{
x = (x << 1) + (x << 3) + (c ^ 48);
c = getchar();
}
return x * f;
}
int main()
{
n = read();
k = read();
for (int i = 1; i <= n * 3; i++) fa[i] = i;
for (int i = 1; i <= k; i++)
{
opt = read(), x = read(), y = read();
if (x > n || y > n)
{
cnt++;
continue;
}
xa = find(x);
xb = find(x + n);
xc = find(x + n * 2);
ya = find(y);
yb = find(y + n);
yc = find(y + n * 2);
if (opt == 1)
{
if (xb == ya || xc == ya)
{
cnt++;
continue;
}
fa[xa] = ya;
fa[xb] = yb;
fa[xc] = yc;
}
else
{
if (xa == ya || xc == ya || yc == xb)
{
cnt++;
continue;
}
fa[xb] = ya;
fa[yc] = xa;
fa[yb] = xc;
}
}
printf("%d", cnt);
return 0;
}