题目
题目来源:CCF NOI2001;(模拟赛 T3)
评测地址:Luogu#2024。
题目描述
动物王国中有三类动物 A、B、C,这三类动物的食物链构成了有趣的环形。A 吃 B,B 吃 C,C 吃 A。
现有 (N) 个动物,以 (1) 到 (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) 句话。
第二行开始每行一句话(按照题目要求,见样例)。
输出格式
一行,一个整数,表示假话的总数。
数据规模和约定
评测时间限制 (1000 extrm{ms}),空间限制 (128 extrm{MiB})。
(1le Nle 5 imes 10^4),(1le Kle 10^5)。
分析
题意是指有一组元素,每个元素有 (3) 种状态。现在给出一系列元素的状态依赖关系,形如「如果 (A) 是 xxx
,那么 (B) 就是 yyy
,反之亦然。」
(考虑一下为什么这与原题是等价的,这很重要)
也就是说,这道题要求我们维护一个状态,其中有若干状态是 必须并存 的。这就是一个集合合并问题,可以使用并查集。
我们将一个动物拆分成 (3) 种状态,每一种就是一个节点。我们用并查集维护其中的并存关系。
为表示方便,下面用 (A_t) 表示一个节点:
- A 表示节点编号,也就是动物编号 (i);
- t 表示对应的类型,是 A、B、C 中的一个。
比如说 (i) 和 (j) 是同类,那么就可以合并 (i_A) 和 (j_A),(i_B) 和 (j_B),以及 (i_C) 和 (j_C)。
那么如果 (i) 吃 (j),那么就可以合并 (i_A) 和 (j_B),(i_B) 和 (j_C),以及 (i_C) 和 (j_A)。
判断是否真假,只需要看看除了给出条件以外的 (i) 和 (j) 节点是否存在同一集合的情况。有就是假话。
比如说已经知道了 (i) 吃 (j),(j) 和 (k) 又是同类,可以得到 (i_A) 和 (k_B) 在一个集合内。此时如果说给出 (k) 吃 (i)(即 (k_B) 与 (i_C) 在同一集合内),就会矛盾。
这样,我们就可以利用并查集,在 (Theta(nalpha(n))) 的时间内愉快 AC。
Code
由于要判断,这次的代码有亿点长……
#include <cstdio>
#include <cctype>
using namespace std;
const int max_n = 50000;
int ufs[max_n*3]; // 并查集别忘了开 3 倍大小
inline int pack(int id, int lat) { return id + lat * max_n; } // 编号
int find(int x) // 查找,最好加上路径压缩
{
if (x != ufs[x])
ufs[x] = find(ufs[x]);
return ufs[x];
}
void unite(int a, int b) // 合并
{
ufs[find(a)] = find(b);
}
bool isn_1(int a, int b) // 是否(不)是同类
{
if (find(pack(a, 0)) == find(pack(b, 1)))
return true;
if (find(pack(a, 0)) == find(pack(b, 2)))
return true;
if (find(pack(a, 1)) == find(pack(b, 0)))
return true;
if (find(pack(a, 1)) == find(pack(b, 2)))
return true;
if (find(pack(a, 2)) == find(pack(b, 0)))
return true;
if (find(pack(a, 2)) == find(pack(b, 1)))
return true;
return false;
}
bool isn_2(int a, int b) // 是否(不)是 a 吃 b
{
if (find(pack(a, 0)) == find(pack(b, 0)))
return true;
if (find(pack(a, 0)) == find(pack(b, 2)))
return true;
if (find(pack(a, 1)) == find(pack(b, 0)))
return true;
if (find(pack(a, 1)) == find(pack(b, 1)))
return true;
if (find(pack(a, 2)) == find(pack(b, 1)))
return true;
if (find(pack(a, 2)) == find(pack(b, 2)))
return true;
return false;
}
inline int read()
{
int ch = getchar(), t = 1, n = 0;
while (isspace(ch)) { ch = getchar(); }
if (ch == '-') { t = -1, ch = getchar(); }
while (isdigit(ch)) { n = n * 10 + ch - '0', ch = getchar(); }
return n * t;
}
int main()
{
int n = read(), k = read(), opt, ta, tb, ans = 0;
for (int i = 0; i < n; i++) // 并查集初始化
{
ufs[pack(i, 0)] = pack(i, 0);
ufs[pack(i, 1)] = pack(i, 1);
ufs[pack(i, 2)] = pack(i, 2);
}
for (int i = 0; i < k; i++)
{
opt = read(), ta = read() - 1, tb = read() - 1;
if (ta < 0 || ta >= n || tb < 0 || tb >= n) // 越界
{
ans++;
continue;
}
if (opt & 1) // 同类标记
{
if (isn_1(ta, tb)) // 谎言
{
ans++;
continue;
}
unite(pack(ta, 0), pack(tb, 0)); // 更新
unite(pack(ta, 1), pack(tb, 1));
unite(pack(ta, 2), pack(tb, 2));
}
else // 梅开二度
{
if (isn_2(ta, tb))
{
ans++;
continue;
}
unite(pack(ta, 0), pack(tb, 1));
unite(pack(ta, 1), pack(tb, 2));
unite(pack(ta, 2), pack(tb, 0));
}
}
printf("%d
", ans); // 精准结束
return 0;
}