并查集
time_keeper · · 个人记录
并查集
- 一种管理元素所在集合的数据结构。
引入
P3367 【模板】并查集
维护一个数据结构,支持合并集合,查询。
考虑最暴力的做法:
维护
复杂度
期望得分:
我们来观察一下我们要干的操作。
如果我们把每个集合内的元素当做节点,把每个集合当做一颗树。
那这其实是一个森林。
对于合并两个集合,本质上是把点
查询是查询他们两个是不是在同一颗树上。
那么我们又得到了一个做法:
每个点维护自己的父亲节点的编号。
根节点的父亲为自己。
查询的时候只需要向上跳直到根节点,如果他们在一个树内,那么他们所在的这棵树的根节点就相同。
合并只需要将一颗树的根节点练到另一颗树的根节点。
具体的,把一颗树的根节点当做另一颗树根节点的儿子。
分析一下复杂度,不难发现,暴力向上跳的复杂度最劣是
那还有哪里能优化呢?
路径压缩
我们注意到,对于每个节点,我们有些时候并不需要真的关心他的父节点是谁,我们要查询的只是他所在树的根节点。
那么就有一个想法,如果说我们把每一个点都直接连到根节点,那么对于处理好的节点,查询就是
这个东西的复杂度通常是
写作
但是这个东西,你不能相信他的时间复杂度。
Tarjan 给出了一种卡到
大概的意思就是,对于二项树一直展开,使每次的路径压缩只压缩一个点。
按秩合并
一般用的都是启发式合并,即按树的大小合并。
如果说我们现在有两颗树,要求合并。
把一颗树挂在另一颗身上。
现在我们把小的树挂在大的树上面,需要更新的节点树最小。
我们注意到,这样做的树高最多是
不妨这样想:
设
则有
有如下递推:
则有
可以证明
对
也就是说,按秩合并每次树高最多增加
设
故
复杂度
建议把路径压缩和按秩合并一起用。
这样的复杂度是
可以防止被卡。
现场写代码(应该不至于调不出来吧???)
带权并查集
我们在刚刚的按秩合并中记录了联通块的大小。
这个
P1196 [NOI2002] 银河英雄传说
维护每个战舰到队首的距离(即到根的距离)。
不难发现,合并两个树。
在
本质上就是从自己到队首走一遍。
因此在回溯的时候统计答案即可。
P4079 [SDOI2016] 齿轮
容易发现,如果图中没有环,一定合法。
有环的话,当且仅当环上传动比的积为
这个证明是容易的。
如果
那么
那么我们就可以考虑并查集的做法。
我们维护权值表示从
那么相邻两个点之间的传动比就是他们权值的商。
如果加入一条边,而两个端点在同一个集合内,就说明这条边在环上。
用
如果
我们要在
我们需要让
把
离线统计联通块
P1197 [JSOI2008] 星球大战
发现在线做不好做,并查集很难分裂一颗树。
那么我们考虑离线。
把所有询问执行一遍,扒下来。
现在我们有把所有操作执行完的最后的图。
现在我们要支持的就是加边,统计联通块数了。
这个是好做的。
加一条边,如果两个端点都在一个联通块内,那么啥也没用。
如果不在一个联通块内,那么这两个联通块就被合并起来了,联通块个数减一。
双倍:P6121 [USACO16OPEN] Closing the Farm G
种类并查集
在原本的并查集中,我们可以维护的是一对
P1892 [BOI2003] 团伙
在这里,敌人的敌人是朋友,朋友的朋友是朋友。
那么我们就可以把一个点拆为两分。
一份表示
我们来想一想。
如果
那么这就保证了无论我们查询的是
如果
就保证了,无论
那么来思考是否具有传递性。
如果
那么我们会连接
此时查询
如果
那么
满足传递。
所以就可以这么做了。
P2024 [NOI2001] 食物链
同上。
我们要维护
如果
如果
并查集的 trick
P2391 白雪皑皑
区间染色,最后单点查询。
显然是可以用各种方法做到
但是此题
本着正难则反的思想。
我们把操作倒着做。
-------(1)
-------(2)
----(3)
---------(4)
如果倒着做,那么之前染过色的区间就不会被再染色了。
那么我们就可以记录每个点之后的第一个未染色的点是哪个。
这样每次我们就只需要从
对中途未染色的染色。
考虑势能分析。
每一个点最多被更新一次。
所以复杂度为
那问题就变成了如何快速维护一个点后(包括这个点)的第一个未染色的点。
那么我们的操作就形似:
把所有下一个能染色的点为
查询。
容易发现这就是一个并查集。
复杂度
可撤销并查集
我们来思考一下并查集的本质。
在路径压缩的时候,我们把
在按秩合并的时候,我们把小的合并到大的上去,并且用势能分析法证明了它的树高最多为
但是按秩合并有一个很优美的性质!
我们注意到,按秩合并的
那么我们对
那么我们就可以把每次操作的修改给存下来,压到一个栈里。
如果这次修改不想要了,我们就弹栈顶,顺带回撤修改。
好像没有很简单的可撤销并查集的。
那就放一个可持久化并查集硬做吧。
P3402 可持久化并查集
这个东西当然是可以用可持久化数组+按秩合并做的。
可持久化数组是:我们注意到修改一个点最多修改
log_V 个权值线段树上的节点,所以可以依照之前版本可持久化。(自己写的,不一定准确)
但是真的需要吗?不需要!
我们观察题目难做的地方在哪里?
我们可能撤销撤销的操作(即加入之前撤销的边)。
我们考虑把操作建成一颗树。
如果是
表示
如果是
我们注意到,这个操作树有一个很优美的性质!
对于任意一个点
那么我们就可以用
具体的,把所有操作扒下来,建出操作树。
然后从根节点开始
这样我们就能保证到点
然后就得到了一个
当然,你也可以用可持久化并查集的做法过掉 NOI2018D1T1。
P4768 [NOI2018] 归程
这个东西是可以用Kruskal重构树上倍增做的。
但是作为学数据结构学傻了的人,我们考虑用可持久化并查集做。
注意注意!这个东西不能用可撤销并查集做!!!
首先我们注意到。
下车之后无论是不是有积水的边都能走,所以下车后一定要走的路程是下车到的点到
这个东西一定一定要用
由于车只能走没有积水的边。
所以我们容易想到一个暴力的想法!
把所有不积水的边塞到并查集里。
维护联通块内到点
但显然过不去。
考虑优化。
注意到每次询问给定了一个积水量。
每次只有海拔大于这个积水量的边才有效。
我们把所有边按照海拔排序。
容易发现,一条边只有一个前缀是有效的!
我们把边按照海拔从大到大的加入并查集中。
我们钦定这个顺序就是时间顺序。
显然我们会有
对于每次询问,我们需要知道他在哪个版本的并查集生效了。
然后查询联通块最小值即可。
复杂度
还有一个小优化:
我们并不需要真的一个可持久化并查集。
因为我们没有撤销操作。
所以我们可以用一个可持久化数组直接代替
复杂度
然鹅
如果这个题的询问可以离线。
我们就可以直接用并查集做了!
因为我们只需要按海拔加边,然后查询所有在这个版本生效的询问。
复杂度
倍增并查集
我也不知道这个东西叫什么。
P3295 [SCOI2016] 萌萌哒
我们维护并查集的联通块表示这一个联通块内的所有位相同。
但是此时需要区间合并。
对于区间合并,我们想到可以用倍增的思想来
显然并查集的区间是具有结合律的。
所以我们合并
设
由于恒等式
我们在合并的时候只需要将对应
比如合并
我们要合并
再合并
然后在最后,我们从大到小枚举
把这个
最后简单乘法原理。
答案即为
课后作业:
【UR #5】怎样更有力气