题解 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;
}