并查集 & 扩展域并查集
引言
由于蒟蒻在各种平台上都没有找到扩展域并查集这玩意的人话解释,好不容易理解后遂记录之。
又由于之前的坑太多了只得把并查集的一起填了。
并查集
并查集很好理解,就是维护点与点之间的关系的。比如说有 \(n\) 个人,给定 \(m\) 组朋友关系,求 \(a\) 和 \(b\) 是不是朋友。
由于朋友的朋友是朋友,所以最终这 \(n\) 个人就会形成许多个朋友集合。那么并查集要做的就是维护这些元素构成的集合。
具体地说,当我们收到一条朋友关系时,两人所处的集合之间就产生了一座“桥梁”。也就是说两个集合连在一起构成了一个大集合,所以我们只需要连接这两个集合即可。
至于要怎么连,以及如何判断两点是否处于同一个集合,可以考虑维护父亲关系数组。有了这个数组,很显然每个不同集合都会有一个不同的“根”。那么只需要每次连接集合时更新父亲关系即可。
一份简单的代码就出炉了。
int getfa(int u){
if(fa[u]==u) return u;
return getfa(fa[u]);
}
但是这样写太慢了容易被卡,遂有人发明了路径压缩和按秩合并。
- 路径压缩:就是每次查询“根”的时候把树压成高度为二的一坨玩意,因为父亲关系本来就是为了方便找“根”,所以如果高度为二那么只用跳一次。可以想象一个根节点下面挂了一排叶子节点的情景。
int getfa(int u){
if(fa[u]==u) return u;
return fa[u]=getfa(fa[u]);
}
- 按秩合并:可以选择按照该集合子树大小或者深度合并,每次把小的那坨挂到大的那坨的根上,显然更优(代码没写)。
那么整个代码就长这样(路径压缩):
#include<bits/stdc++.h>
using namespace std;
int fa[500005];
int getfa(int u){
if(fa[u]==u) return u;
return fa[u]=getfa(fa[u]);
}
int main(){
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1;i<=m;i++){
int op,a,b;
scanf("%d%d%d",&op,&a,&b);
int af=getfa(a),bf=getfa(b);
if(op==2){
if(af!=bf) printf("N\n");
else printf("Y\n");
}
else{
if(af!=bf) fa[af]=bf;
}
}
return 0;
}
例题:
扩展域并查集
还是刚刚那个例子,但是多了一种关系:敌人关系。
两种关系一个并查集如何处理呢?
其实很简单:设 \(i\) 的第二节点为 \(n+i\)(\(n\) 为人数),\(j\) 的第二节点为 \(n+j\)。那么如果给出的是一组朋友关系,就连接节点 \(i\) 和节点 \(j\);否则就连接节点 \(n+i\) 和节点 \(j\)、节点 \(i\) 和节点 \(n+j\)。
好的恭喜你学会了扩展域并查集。
\(Code\)
#include<bits/stdc++.h>
using namespace std;
int fa[1000005];
int getfa(int u){
if(fa[u]==u) return u;
return fa[u]=getfa(fa[u]);
}
int main(){
int n;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
int opt,u,v;
scanf("%d%d%d",&opt,&u,&v);
int afa1=getfa(a),bfa1=getfa(b);
if(opt==1)
if(afa1!=bfa1) fa[afa1]=bfa1;
else if(opt==2){
int afa2=getfa(n+a),bfa2=getfa(n+b);
if(afa1!=bfa2) fa[afa1]=bfa2;
if(afa2!=bfa1) fa[afa2]=bfa1;
}
else
if(afa1!=bfa1) printf("N\n");
else printf("Y\n");
}
return 0;
}
免责声明:不知道对错与否,没测过。

浙公网安备 33010602011771号