并查集

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 在并查集中的祖先.

以上是关于并查集的主要内容,如果未能解决你的问题,请参考以下文章

数据结构----并查集

LibreOJ #109. 并查集

数据结构--并查集

数据结构--并查集

数据结构----并查集

数据结构----并查集