题解 P2024 【食物链】
本题解适合于并查集初学者(不要有想法),建议嫌文章冗长的巨佬们阅读其他题解。
引入
并查集能维护连通性、传递性,通俗地说,亲戚的亲戚是亲戚
。
然而当我们需要维护一些对立关系,比如 敌人的敌人是朋友
时,正常的并查集就很难满足我们的需求。
这时,种类并查集
就诞生了。
常见的做法是将原并查集扩大一倍规模,并划分为两个种类。
在同个种类的并查集中合并,和原始的并查集没什么区别,仍然表达他们是朋友
这个含义。
考虑在不同种类的并查集中合并的意义,其实就表达 他们是敌人
这个含义了。
按照并查集美妙的 传递性
,我们就能具体知道某两个元素到底是 敌人
还是 朋友
了。
至于某个元素到底属于两个种类中的哪一个,由于我们不清楚,因此两个种类我们都试试。
具体实现,详见 P1525 关押罪犯
。
概念解释
再来看本题,每个动物之间的关系就没上面那么简单了。
对于动物 xx 和 yy ,我们可能有 xx 吃 yy , xx 与 yy 同类, xx 被 yy 吃。
但由于关系还是明显的, 11 倍大小、 22 倍大小的并查集都不能满足需求, 33 倍大小不就行了!
类似上面,我们将并查集分为 33 个部分,每个部分代表着一种动物种类。
设我们有 nn 个动物,开了 3n3n 大小的种类并查集,其中 1 \sim n1∼n 的部分为 AA 群系, n + 1 \sim 2nn+1∼2n 的部分为 BB 群系, 2n + 1 \sim 3n2n+1∼3n 的部分为 CC 群系。
我们可以认为 AA 表示中立者, BB 表示生产者, CC 表示消费者。此时关系明显: AA 吃 BB , AA 被 CC 吃。
当然,我们也可以认为 BB 是中立者,这样 CC 就成为了生产者, AA 就表示消费者。(还有 11 种情况不提及了)
联想一下 22 倍大小并查集的做法,不难列举出:当 AA 中的 xx 与 BB 中的 yy 合并,有关系 xx 吃 yy ;当 CC 中的 xx 和 CC 中的 yy 合并,有关系 xx 和 yy 同类等等……
但仍然注意了!我们不知道某个动物属于 AA , BB ,还是 CC ,我们 33 个种类都要试试!
也就是说,每当有 11 句真话时,我们需要合并 33 组元素。
容易忽略的是,题目中指出若 xx 吃 yy , yy 吃 zz ,应有 xx 被 zz 吃。
这个关系还能用种类并查集维护吗?答案是可以的。
若将 xx 看作属于 AA ,则 yy 属于 BB , zz 属于 CC 。最后,根据关系 AA 被 CC 吃可得 xx 被 zz 吃。
既然关系满足上述传递性,我们就能放心地使用种类并查集来维护啦。
图片解释
理论太难懂?那就结合数据和图片来解释吧!
假如我们有以下的输入数据:
4 5
1 1 3
2 2 4
2 3 2
1 1 4
2 2 1
因为涉及 4 个动物( n = 4n=4 ),所以构建初始并查集如下图:
先看第 11 句话:动物 11 和 33 是同类的。
我们可以在 33 个群系中分别给 11 和 33 的集合合并,以表示动物 11 和 33 是一定友好的。
再看第 22 句话:动物 22 吃 44 。
显然这不是矛盾的。但我们不知道 22 和 44 对应 AA , BB , CC 中的哪个,所以我们只能根据 AA 吃 BB ,合并 AA 群系中的 22 和 BB 群系中的 44 ;再根据 BB 吃 CC 和 CC 吃 AA ,作出对应的处理。结果如下所示:
接着看第 33 句话:动物 33 吃 22 。这是句真话,具体的真假话判断方法看下面两句话。我们暂且先作出以下处理:
第 44 句话中,表明 11 和 44 是同类动物。此时我再解释如何判断话的真假。
对于同类动物,我们转换一下,如果我们知道 11 不吃 44 且 44 不吃 11 ,他们不就同类了吗?
好,那我们的任务就变成:如何判断动物 xx 吃动物 yy ?
反观第 22 句话,我们知道如果要表示动物 xx 吃动物 yy ,只要根据 AA 吃 BB ,把 AA 群系中的 xx 和 BB 群系中的 yy 合并即可。另外 22 次合并暂不讨论。
那反过来,如果 AA 群系中的 xx 已经和 B 群系中的 yy 在同一集合中了,不就表示了动物 xx 吃动物 yy 吗?
于是,我们看到上面那张图, BB 群系中的 11 按照并查集的递归操作,找出自己的终极上级是 AA 群系中的 44 。
分析其含义,属于 BB 群系的 11 已经与 AA 群系的 44 ,应有 44 吃 11 ,而非同类。第 44 句话是假的。
那么第 55 句话, 22 吃 11 。我们需要判断 22 和 11 是否是同类并且 22 是否被 11 吃即可。
判断是否同类,我们同样可以反过来:判断在同个群系中的 22 和 11 的集合是否已经合并。
得出 22 和 11 不是同类后,我们再看 11 是否吃 22 。看图, AA 群系中的 11 和 BB 群系中的 22 在同一集合中。
得出 11 吃 22 。第 55 句话也是假话。
因此,答案就是有两句假话。输出 22 ,问题完美解决。
注意事项
-
种类并查集求的并非具体种类,而是关系!
-
在代码过程中,不要忘了特判编号大于 nn 的情况!
代码实现
如果还没有理解,只能使用最终办法了,上程序!
(当然我还是希望各位摸清种类并查集的本质,灵活运用)
#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; }
撰文不易,不适轻喷!
原文地址:https://www.cnblogs.com/tzr-skywalker/p/11011240.html
- python常见模块之time模块
- U10783 名字被和谐了
- BZOJ 1174: [Balkan2007]Toponyms
- 1355: [Baltic2009]Radio Transmission
- Equation Group(方程式组织)
- Python中下划线---完全解读
- python常见模块之collections模块
- MYSQL之库操作
- 实战-如何获取安卓iOS上的微信聊天记录、通过Metasploit控制安卓
- lightswitch binding custom control
- 3339: Rmq Problem
- Codeforce GYM 100741 A. Queries
- UVA - 11178 Morley's Theorem
- PyMySQL模块的使用
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 为什么不推荐使用PHPicker
- 【C语言简单说】一:第一个C语言程序
- 【C语言简单说】二:第一个C语言程序详解(1)
- 【C语言简单说】二:第一个C语言程序详解(2)
- 【C语言简单说】二:第一个C语言程序详解(3)
- 【C语言简单说】三:整数变量和输出扩展(1)
- 【C语言简单说】三:整数变量和输出扩展(2)
- 【C语言简单说】三:整数变量扩展和输出扩展(3)
- 【C语言简单说】三:浮点数变量和字符变量(4)
- 【C语言简单说】三:变量总结ASCII码扩展(5)
- 【C语言简单说】四:常量
- 【C语言简单说】五:常用运算符
- 【C语言简单说】六:取模运算符以及变量的扩展
- 【C语言简单说】七:自定义函数(1)
- 【C语言简单说】七:自定义函数(2)