[总结] 并查集的二三事

并查集是一个维护集合的数据结构
它能够方便的进行元素集合的合并,并且查询每个元素属于哪个集合
并查集更多的在于关系的传递性,集合与集合之间常常因为一个元素的“搭桥”而合并成为同一个集合

只要1,2,3,A题很简单

并查集(simple)

在使用并查集时,我们一般通过维护父子关系来完成对集合的控制
若两个点的最老祖先相同,则在同一个集合内,否则,不在同一个集合内
若要进行合并集合操作,则直接让一个点的最老祖先只指向另一个点的最老祖先就好了(保证了两个集合内所有点的最老祖先都是同一个点)
并查集的操作很简单,一个 f i n d ,一个 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;
}

显然, f i n d 的最大复杂度为 O ( n ) ,这显然是不能接受的

随便YY一下,你会发现对于一个点来说,反正要找的是他的祖先,而不是找 f a ,所以我们将 f a 在不断搜索时同时更新为他的 g r a n d f a ,最终更新为它的祖先即可

这样复杂度直线下降,可以做到均摊复杂度复杂度 O ( m a l p h a ( n ) )
a l p h a 函数一般来说在很大的范围是不大于 4
所以可以将其看为线性的

g r a n d f a 板子:

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]);
}

这个东西一般被称作路径压缩


带权并查集

诶呀,会并查集了?那你会带权并查集么?

学完普通的并查集,让我们来想想如果并查集中的连边是有边权的咋办?

例题:[NOI2002]银河英雄传说

其实很简单,每个点不仅仅保存一个 f a ,我们还保存一个 d i s ,意味着当前点到 f a 的距离.在路径压缩是或者在查询时记得将 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];
}

可持久化并查集

不错呀,居然连带权并查集都搞定了?那就试试可持久化并查集吧!

要学会可持久化并查集,首先得学会可持久化线段树

接下来实现一下可持久化数组

然后用可持久化数组来维护 f a

恭喜你,你就能够A掉可持久化并查集

PS:其实本来还有个拆点并查集的,但是因为很懒貌似很简单,所以自已意会一下就好啦!
这也就是可以回退操作的并查集哦!

猜你喜欢

转载自blog.csdn.net/force_chl/article/details/80807862