题解 P2024 【食物链】
重写于 2018.9.13,并修改代码码风(以前的码风真是丑啊qwq)
本题解适合于并查集初学者,建议嫌文章冗长的巨佬们阅读其他题解。
引入
并查集能维护连通性、传递性,通俗地说,亲戚的亲戚是亲戚
。
然而当我们需要维护一些对立关系,比如 敌人的敌人是朋友
时,正常的并查集就很难满足我们的需求。
这时,种类并查集
就诞生了。
常见的做法是将原并查集扩大一倍规模,并划分为两个种类。
在同个种类的并查集中合并,和原始的并查集没什么区别,仍然表达他们是朋友
这个含义。
考虑在不同种类的并查集中合并的意义,其实就表达 他们是敌人
这个含义了。
按照并查集美妙的 传递性
,我们就能具体知道某两个元素到底是 敌人
还是 朋友
了。
至于某个元素到底属于两个种类中的哪一个,由于我们不清楚,因此两个种类我们都试试。
具体实现,详见 P1525 关押罪犯
。
概念解释
再来看本题,每个动物之间的关系就没上面那么简单了。
对于动物
但由于关系还是明显的,
类似上面,我们将并查集分为
设我们有
我们可以认为
当然,我们也可以认为
联想一下
但仍然注意了!我们不知道某个动物属于
也就是说,每当有
容易忽略的是,题目中指出若
这个关系还能用种类并查集维护吗?答案是可以的。
若将
既然关系满足上述传递性,我们就能放心地使用种类并查集来维护啦。
图片解释
理论太难懂?那就结合数据和图片来解释吧!
假如我们有以下的输入数据:
4 5
1 1 3
2 2 4
2 3 2
1 1 4
2 2 1
因为涉及 4 个动物(
先看第
我们可以在
再看第
显然这不是矛盾的。但我们不知道
接着看第
第
对于同类动物,我们转换一下,如果我们知道
好,那我们的任务就变成:如何判断动物
反观第
那反过来,如果
于是,我们看到上面那张图,
分析其含义,属于
那么第
判断是否同类,我们同样可以反过来:判断在同个群系中的
得出
得出
因此,答案就是有两句假话。输出
注意事项
-
种类并查集求的并非具体种类,而是关系!
-
在代码过程中,不要忘了特判编号大于
n 的情况!
代码实现
如果还没有理解,只能使用最终办法了,上程序!
(当然我还是希望各位摸清种类并查集的本质,灵活运用)
#include <cstdio>
inline int read() {
char c = getchar(); int n = 0;
while (c < '0' || c > '9') { c = getchar(); }
while (c >= '0' && c <= '9') { n = (n << 1) + (n << 3) + (c & 15); c = getchar(); }
return n;
}
const int maxN = 100005;
int n, m, ans, fa[maxN * 3];
int find(int u) { return fa[u] == u ? u : fa[u] = find(fa[u]); }
int main() {
n = read(), m = read();
for (int i = 1; i <= n * 3; i++) { fa[i] = i; }
for (; m; m--) {
int opt = read(), u = read(), v = read();
if (u > n || v > n) { ans++; continue; }
if (opt == 1) {
if (find(u + n) == find(v) || find(u) == find(v + n)) { ans++; }
else {
fa[find(u)] = find(v);
fa[find(u + n)] = find(v + n);
fa[find(u + n + n)] = find(v + n + n);
}
} else {
if (find(u) == find(v) || find(u) == find(v + n)) { ans++; }
else {
fa[find(u + n)] = find(v);
fa[find(u + n + n)] = find(v + n);
fa[find(u)] = find(v + n + n);
}
}
}
printf("%d\n", ans);
return 0;
}