并查集是一个维护集合的数据结构
它能够方便的进行元素集合的合并,并且查询每个元素属于哪个集合
并查集更多的在于关系的传递性,集合与集合之间常常因为一个元素的“搭桥”而合并成为同一个集合
只要1,2,3,A题很简单
并查集(simple)
在使用并查集时,我们一般通过维护父子关系来完成对集合的控制
若两个点的最老祖先相同,则在同一个集合内,否则,不在同一个集合内
若要进行合并集合操作,则直接让一个点的最老祖先只指向另一个点的最老祖先就好了(保证了两个集合内所有点的最老祖先都是同一个点)
并查集的操作很简单,一个
find
f
i
n
d
,一个
merge
m
e
r
g
e
即可
最简单的板子:
int find(int x) {
if(fa[x]==x) return fa[x];
else return find(fa[x]);
}
void merge(int x,int y) {
int fx=find(x),fy=find(y);
fa[fx]=fy;
}
显然, find f i n d 的最大复杂度为 O(n) O ( n ) ,这是不能接受的
随便YY一下,你会发现对于一个点来说,反正要找的是他的祖先,而不是找 fa f a ,所以我们将 fa f a 在不断搜索时同时更新为他的 grandfa g r a n d f a ,最终更新为它的祖先即可
这样复杂度直线下降,可以做到均摊复杂度复杂度
O(m∗alpha(n))
O
(
m
∗
a
l
p
h
a
(
n
)
)
alpha
a
l
p
h
a
函数一般来说在很大的范围是不大于
4
4
的
所以可以将其看为线性的
找板子:
int find(int x) {
if(fa[x]==x) return fa[x];
else return fa[x]=find(fa[x]);
}
或者简化版:
int find(int x) {
return fa[x]==x?fa[x]:fa[x]=find(fa[x]);
}
这个东西一般被称作路径压缩
带权并查集
诶呀,会并查集了?那你会带权并查集么?
学完普通的并查集,让我们来想想如果并查集中的连边是有边权的咋办?
其实很简单,每个点不仅仅保存一个
fa
f
a
,我们还保存一个
dis
d
i
s
,意味着当前点到
fa
f
a
的距离.在路径压缩是或者在查询时记得将
dis
d
i
s
合并即可
但是注意,原来的简化写法就不行了
老老实实的写递归*or非递归*版吧
int find(int x) {
if(x!=fa[x]) {
int temp=fa[x];
fa[x]=find(fa[x]);
dis[x]+=dis[temp];
}
return fa[x];
}
可持久化并查集
不错呀,居然连带权并查集都搞定了?那就试试可持久化并查集吧!
要学会可持久化并查集,首先得学会可持久化线段树
接下来实现一下可持久化数组
然后用可持久化数组来维护 fa f a
恭喜你,你就能够A掉可持久化并查集
PS:其实本来还有个拆点并查集的,但是因为很懒貌似很简单,所以自已意会一下就好啦!
这也就是可以回退操作的并查集哦!