我们都知道,并查集的路径压缩大大加速了操作的时间复杂度,但是如果出题人足够毒瘤,仍然可以把你卡掉。
比如说你在1
号点上连了2
、3
、4
、5
, 这时候进来个6
,您觉得应该将1
连到6
上更优呢?还是反过来更优呢?
基于减少复杂度的贪心思想,我们当然希望能够把一棵较小的并查集并入较大的一棵当中。
那我们就多维护一个siz[root]
,表示当前这棵以root
为根的并查集的大小或深度。
这样,我们以siz
作为关键字进行判断谁应该合并到谁的父亲上。
更进一步
开两个数组好累啊 QwQ
那好,我们可以只开一个!
通过观察,我们发现这棵并查集,它的根节点的父亲fa[root]
是没有用的,他唯一的功能是判断到头了没有。
那我们可以令fa[root] = siz[root]
。
当然,也许会问,如果是这样会不会造成无法判断根,数组越界?
说的好!
那我们可以把他改成这样:fa[root] = -siz[root]
。
一旦发现fa[x] < 0
,就立刻发现不对劲!节点编号不可能是负数!那一定是到头啦,于是我们愉快的把x
揪了出来,他就是根节点!
看起来细节还蛮多的嘛!
可不是嘛!代码写起来细节更多!尤其是如果写成非递归的形式,那么你可能要提前判一下fa[fa[x]]
是否是负数!小心跳过头了哦!
实战!
以子树大小为关键字
在 main()
中
1 | read(n), read(m); |
f()
函数
1 | const int N = 1e4 + 5; |
以深度为关键字
在 main()
中
1 | read(n), read(m); |
f()
函数
与第一种情况的相同