并查集
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了并查集相关的知识,希望对你有一定的参考价值。
按秩合并
并查集的优化有两个: 按秩合并, 路径压缩. 按秩合并相比于路径压缩, 效率较低, 但是它可以支持撤销操作.
按秩合并: 设 s[x] 为点 x 的秩, 合并的时候对于 x, y, s[x] < s[y] , 将 x 的父亲设为 y .
直观理解: 秩反应的是容纳水平, 秩越高, 容纳水平越高, 这样才能更加均衡地填满.
[HDU 5354] Bipartite Graph 分治
题意
给定一张 $n$ 个点, $m$ 条边的无向图.
问删去每个点后, 原图是不是二分图.
$1 \le n, m \le {10} ^ 5$ .
分析
一个图是二分图 $\Leftrightarrow$ 图中不存在奇环.
判定一个图是不是二分图, 可以使用并查集, 多维护一个当前点与父亲的关系的量 bond .
删除每一个点, 我们有两种维度: 区间加法, 区间减法. 这里考虑加法.
可以直接分治, 每次连接外面的边.
也可以建线段树, 每个节点存储删除这个区间的点时, 共同拥有的边, 然后进行 DFS .
实现
支持撤销: ① 以直接开一个栈, 并且记录一下时间戳; ② 或者整体地观察一下哪些地方发生了修改, 修改回去就好了.
路径压缩
给定一个序列, 每次访问一个区间, 如果一个点被访问过了, 那么不再访问.
给定一棵树, 每次访问某个点到根的路径上的所有点, 如果一个点被访问过了, 那么不再访问. --- ①
把握本质, 都是这样的问题: 给定一个 每个点有且仅有一个后继的结构 , 每次访问某个点到根的路径上的所有点, 如果一个点被访问过了, 那么不再访问.
我们还有一些变式, 比如说: 给定一棵树, 每次访问某条路径上的所有点, 如果一个点被访问过了, 那么不再访问.
通过求出路径的两个端点的 LCA , 我们就可以转化到①.
对于这类的问题, 我们如果能够做到路径压缩, 那么就能将算法的时间复杂度优化到均摊线性.
幸运的是, 我们可以利用并查集进行路径压缩:
1. 如果当前超出了终点, 那么返回终点.
2. 将当前点进行访问.
3. 访问当前点的下一个点, 并求出祖先 t .
4. 标记当前点的下一个访问点为 t .
有时候也不用这么死板, 对于变式, 我们可以直接将两个点一起往上跳.
这类问题有一个经典的模型.
若干次区间赋值, 每次将区间 [l, r] 赋值 w , 一个点最终的值为区间的值的最小值, 求每个点最终的值.
我们可以通过排序, 然后再利用路径压缩的技巧在 O(n log n) 解决.
这样写的优点在于跑得快, 代码短, 性价比高.
[BZOJ 2054] 疯狂的馒头
题意
m 次将某个区间进行染色, 求每个区间最后的颜色.
n <= 10 ^ 6, m <= 10 ^ 7 .
分析
逆序处理, 路径压缩.
[BZOJ 3319] 黑白树
题意
给定一棵树, 每条边有黑, 白两种颜色中的一种, m 次操作:
1 x 询问点 x 向上找到的第一条黑边.
2 x y 将 x 到 y 的路径上的所有边染成黑色.
n <= 1000000 .
分析
" 点 x 向上找到的第一条黑边 " , 如果将白边视作有边, 黑边视作没有边, 我们相当于找到连通块的深度最小的点, 而操作 2 相当于把某些边断开.
离线处理, 回代, 变为连边的问题, 用并查集即可处理.
对于回代的预处理, 我们通过路径压缩, 即可处理出每条边被染黑的时间, 以及最后仍然是白色的边.
小结
注意路径压缩后, f[x] 不一定是下一个没有访问的点, 但是没有访问的点一定可以通过 f 访问到.
双向链表
通过并查集可以实现只支持删除的双向链表, 本质就是进行路径压缩.
[BZOJ 3211] 花神游历各国
题意
区间开方, 区间求和.
分析
一个数开方的次数比较小. 对于区间开方操作, 我们暴力枚举区间 [l, r] 的每个不等于 1 的位置进行开方. 我们只需要使用双向链表对这个结构进行维护.
对于查询, 我们再维护一个树状数组.
妙用并查集
[BZOJ 1854] [SCOI 2010] 游戏
题意
给定 n 对 {a, b} , 每对中选择一个, 求从 1 开始的最长值域连续段的长度.
n <= 1000000 .
分析
对值域建图.
把每个值当作图中的一个点.
对于点对 {a, b} , 将点 a 与点 b 进行连边.
每条边可以配对一个点, 求最大的 x , 满足前 x 个点可以被不同的边进行配对.
考虑一个连通块.
如果这个连通块是一棵树, 那么可以对任意的 n-1 个点进行配对, 由于我们求从 1 开始的最长值域连续段, 所以对前 n-1 小的点进行配对, 最大的点不能被配对.
如果这个连通块至少有一个环, 那么所有点都可以进行配对.
我们只需要找到某个点 id , 它所在的连通块是一棵树, 且 id 是这个连通块中最大的点, 那么答案为 id-1 .
[BZOJ 4423] Bytehattan
题意
给定一个 $n \times n$ 的点阵, 形成一个网格图.
最初的时候四连通.
$m$ 次操作, 每次删去一条边 $(u, v)$ , 问 $u$ 和 $v$ 是否仍然连通.
$2 \le n \le 1500, 1 \le m \le 2n(n - 1)$ .
分析
将平面图转化为它的对偶图.
每次相当于将两个平面区域合并.
对于删去一条边, 若与它相邻的两个区域连通, 那么操作后两个点中有一个点被区域包围, 一个点被隔在了外面, 所以不连通, 反之仍然连通.
[SCOI2016] 萌萌哒
题意
求有多少个无前导零的 $N(N\le 10^5)$ 位数 $A$ , 满足 $M(M\le 10^5)$ 个限制条件 $L~R~X~Y$ : $A[L+i] = A[X+i]$ .
分析
对 ST 表用并查集, 然后标记下传.
带权并查集 / 关系并查集
[BZOJ 3376] 方块游戏
题意
给定 30000 个方块, q 次操作:
M x y: 将标号为 x 的方块所在的堆, 叠到标号为 y 的方块所在的堆之上.
C x: 求标号为 x 的方块下有多少个方块.
分析
带权并查集.
两个在同一堆的方块在一个并查集内, f[x] 在 x 下.
维护 f[x]: x 在并查集的父亲.
s[x]: x 为代表元素, 集合的大小.
w[x]: x 与父亲之间有多少个方块.
[BZOJ 1370] 团伙
题意
人之间有且仅有两种关系: 朋友, 敌人.
朋友的朋友是朋友, 敌人的敌人是朋友.
确定是朋友的一些人组成一个团伙.
给定 n 个人之间的关系, 求最多有多少个团伙?
分析
维护 b[x] 表示 x 与 f[x] 的关系.
[BZOJ 2303] [APOI 2011] 方格染色
题意
在 n * m 的方格中, 每个格子要填上 0 或 1, 且满足任意一个四方格中, 有 1 个 1 或者 3 个 1 .
已知某些格子中填的数, 求有多少种合法的填法.
n, m <= 10 ^ 6 .
分析
探究合法填法的一些性质.
a[x, y] = a[x-1, y-1] ^ a[x, y-1] ^ a[x-1, y] ^ 1 .
将 a[2 ~ x][2 ~ y] 异或起来, 可以得到 a[x, y] = a[1, 1] ^ a[x, 1] ^ a[1, y] ^ [(n-1) * (m-1) 为奇数] .
假如我们能够确定第一行, 第一列, 那么所有的格子都确定了.
枚举 a[1, 1] 的取值, 那么限制条件变为二元的限制条件 a[x, 1] ^ a[1, y] = c .
我们将 (x, 1) 与 (1, y) 的关系通过并查集维护, 最后查询 2 ^ {连通块个数 - 1} .
[BZOJ 1202] 狡猾的商人
f[x] < x .
维护 b[x] = sum(f[x], f[x]+1, ..., x) .
Kruskal 与 Kruskal重构树
Kruskal重构树用于这样一个核心模型 :
给定 n 个点, m 条带权边的无向图. 在线 求在所有边权不超过 d 的边的作用下, 某个点或某两个点的连通性信息.
我们还可以与这样一个 "可持久化并查集" 的例子进行联系:
n 个点, 支持连边, 和查询在前 t 次操作下, 某两个点是否连通.
不难发现, "可持久化并查集" 的例子隐藏起了时间这一维, 只需要将时间量化就可以完成转化: 在时刻 t 将某两个点连边, 就相当于 x 与 y 之间存在一条边, 边权为 t .
先介绍除了经典的Kruskal重构树的做法之外, 自己琢磨出来的一种做法.
我们考虑将边权从小到大进行排序, 不断加边和查询, 用并查集进行维护.
如果不要求强制在线, 我们直接离线处理即可.
现在要求强制在线, 我们考虑将并查集的功能进行拓展, 进而支持维护历史信息.
为了更好地将功能进行拓展, 我们不再使用路径压缩, 而是使用按秩合并.
多记录一个 w 数组, 在并查集中, 当点 x 的父亲指向点 y 的时候, 记录 w[x] = 边权 .
那么我们在查找一个节点 x 在不超过 d 的边权的作用下的并查集时, 只需要将 x 在并查集中从下往上跳, 直到恰好跳到满足 w[x] > d 时停止, 就能找到集合的代表元素.
如果需要查询连通块内的信息, 我们就需要进行可持久化线段树合并, 对每个点开一个 map 来记录所有的根.
现在介绍Kruskal重构树.
每次合并的时候, 我们不再按秩合并, 而是新建一个点 cur , 将两个代表元素的父亲指向当前这个新建点 cur .
那么找 x 在不超过 d 的边权的作用下的代表元素时, 不能直接往上跳, 改用树上倍增.
如果需要查询连通块内的信息, 我们可以按照上述方法进行可持久化线段树合并, 这时候每个点对应的根唯一, 不再需要开 map . 或者我们可以将 Kruskal 重构树进行 dfs 序剖分, 转化为序列的问题.
比较一下两种写法的优缺点:
自己的写法速度较快, 查询代表元素可以直接查询, 不支持 dfs 序剖分, 需要开 map .
Kruskal重构树速度较慢, 查询代表元素需要树上倍增, 支持 dfs 序剖分, 不需要开 map .
[BZOJ 3551] Peaks
模板题.
[BZOJ 4668] 冷战
题意
两种操作:
0 x y 将 x 和 y 进行连边.
1 x y 询问 x 和 y 在什么时候刚刚连通.
n, m <= 500000, 强制在线.
分析
按秩合并.
维护信息 w[x] 表示 x 合并到 f[x] 的时间.
对于查询暴力找 LCA , 并求路径的 w[x] 的最大值 (其实必然是最上面的两个点中的一个) .
Tarjan算法 - 离线LCA
离线算法, 用于求出若干组点 (x, y) 的 LCA , 效率极高.
先将所有询问读入, 然后对树进行 DFS . 回溯的过程中将 f[son] 赋为 cur ; 访问完一个节点时进行查询, 对于 (cur, x) , 若 x 已被访问, 那么查询 x 在并查集中的祖先.
以上是关于并查集的主要内容,如果未能解决你的问题,请参考以下文章