【题解】食物链
题目
题目来源: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\ \textrm{ms}\),空间限制 \(128\ \textrm{MiB}\)。
\(1\le N\le 5\times 10^4\),\(1\le K\le 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(n\alpha(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\n", ans); // 精准结束
return 0;
}
本文来自博客园,作者 5ab,转载请注明链接哦 qwq
博客迁移啦,来看看新博客吧 -> https://5ab-juruo.oier.space/