并查集
基础并查集
(tips:求最小生成树的Kruskal算法也需要用到并查集。)
查询操作
下面是并查集的几种实现方式。
递归版本:
1 2 3 4 |
|
可以精简为一行:
1 2 3 |
|
while循环版本:
1 2 3 4 |
|
由于省去了调用函数的环节,这个写法相比递归版速度会更快,不过两者相差不大。
合并操作:
1 2 3 4 |
|
将两个元素归为一个集合中。
例题
P2814 家谱(需要使用哈希或map)
种类并查集
同时维护多个种类的关系。
例如你需要维护朋友和敌人这两个关系,我们可以将普通并查集的范围扩大至两倍,1n用来维护朋友关系,n+12n用来维护敌人关系,然后根据题意加以操作即可。
下面给出实现以上操作的部分代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
例题
P2024 [NOI2001] 食物链(需要维护三个种类关系)
P1525 [NOIP2010 提高组] 关押罪犯(也可以用二分图判断来做)
P1955 [NOI2015] 程序自动分析(数据范围较大,需要使用离散化或哈希)
带权并查集
额外维护两个数组d和siz,d用来记录当前节点到其父亲节点的距离,siz用来记录祖宗节点所在集合中的节点个数。
在路径压缩的同时更新d数组来统计节点到根的路径信息。
查询操作:
1 2 3 4 5 6 |
|
合并操作:
1 2 3 4 5 6 7 |
|
初始化:
1 2 3 4 5 |
|
注意一些细节。当我们需要求某点到根节点距离时,需要调用一次find函数进行路径压缩,否则求出的值可能并非之前操作更新后的真实值(详见洛谷P5092)。
拓展
我们考虑以下情况:
给定\(n\)个变量 \(a_1\)~\(a_n\),m个约束,每个约束形如(\(x,y,c\))表示 \(a_x-a_y=c\),判断是否存在可行解。
解法1
将每个约束拆成 \(a_x-a_y\ge c\) 和 \(a_x-a_y\le c\),使用查分约束。
但是有可能被数据卡到 \(O(nm)\)。
解法2
使用带权并查集。
可以在线性的时间内解决该问。
对于每个约束(\(x,y,c\)),分两种情况考虑:
- 若 \(x\) 和 \(y\) 不在同一连通块内,从 \(x\) 向 \(y\) 连一条权值为 \(c\) 的边。
- 若 \(x\) 和 \(y\) 在同一连通块内,只要判断( \(x\) 到根路径权值和)减去( \(y\) 到根路径权值和)是否为 \(c\) 即可。
此时的合并操作变为:
1 2 3 4 5 6 |
|
当数据范围过大或维护种类过多导致所需空间较大时,我们可以使用权值并查集来维护种类关系。
例如洛谷P2024,对于每句话中的 \(i\) 和 \(j\) :
如果 \(i\) 和 \(j\) 不在同一集合(合并操作):
-
如果 \(i\) 和 \(j\) 为同类,则从 \(i\) 向 \(j\) 连一条权值为 \(0\) 的边;
-
如果 \(i\) 是 \(j\) 的天敌,则从 \(i\) 到 \(j\) 连一条权值为 \(1\) 的边;
-
如果 \(i\) 是 \(j\) 的猎物,则从 \(i\) 到 \(j\) 连一条权值为 \(2\) 的边。
如果 \(i\) 和 \(j\) 在同一集合(询问操作):
-
如果 \(i\) 和 \(j\) 是同类,则 \(d_x=d_y\) 时为真话,反之为假话;
-
如果 \(i\) 是 \(j\) 的天敌,则 \((d_x-d_y+3)\%3=1\) 时为真话,反之为假话;
-
如果 \(i\) 是 \(j\) 的猎物,则 \((d_x-d_y+3)\%3=2\) 时为真话,反之为假话。
(本题并不需要” \(i\) 是 \(j\) 的猎物“这样的操作,写上是为了能够更好地理解)
(先 \(+3\) 再对 \(3\) 取模是为了防止出现负数,对判断产生干扰)
由于我们维护的是三个种类的关系,这里我们需要对查询操作和合并操作加以修改:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
实际上只是多了一个对权值的取模操作,其他不变。
为方便读者更好地理解以上内容,这里给出一份代码供大家调试。
例题
P5092 [USACO04OPEN] Cube Stacking
by loynyng_fasfy