并查集
概念
一张图,想要判断其中两个点是否连通,用遍历来判断非常耗时。这时我们只需要把所有节点向上回溯,找到一个“最老的祖先”,然后当需要判断时,判断祖先是否一致就可以了,即判断是否在一个并查集内。
当我们想要合并两个并查集时,只需要将其中一个并查集的“祖先”与另一个并查集创建连通关系就可以了。
代码
首先是初始化。一开始时,我们不知道那些节点是连通的,因此每个点的祖先都是他自己。
因此对于每个f[i],它的初始值都是i。
void init(){
for(int i=1;i<=n;i++)
f[i]=i;
}
然后是寻找节点的祖先。对于任意一个节点,其祖先和其父亲节点的祖先必然是一致的。因此寻找时只需要不断向上寻找父亲节点,直到找不到为止即可。
int fd(int x){
if(f[x]==x)
return x;
return f[x]=fd(f[x]);
}
最后是合并。只需要把一个元素的祖先设为另一个元素的祖先即可。
void Union(int x,int y){
x=fd(x);
y=fd(y);
f[x]=y;
}
例题
这个题就是将所有亲戚创建联通关系,然后判断是否为一个并查集。
代码如下:
#include<bits/stdc++.h>
#define TIE ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
using namespace std;
int n,m,p,a,b,c,d,f[5005];
void init(){for(int i=1;i<=n;i++)f[i]=i;}
int fd(int x){
if(f[x]==x)return x;
return f[x]=fd(f[x]);
}
void Union(int x,int y){x=fd(x);y=fd(y);f[x]=y;}
signed main(){
TIE;cin>>n>>m>>p;init();
for(int i=1;i<=m;i++){cin>>a>>b;Union(a,b);}
for(int i=1;i<=p;i++){
cin>>c>>d;
if(fd(c)==fd(d))cout<<"Yes\n";
else cout<<"No\n";
}
return 0;
}
带权并查集
点带权并查集
在这道题中,我们不只需要判断其是否为同一个家族,还需要判断家族大小。
我们可以额外创立一个数组,来记录每个家族的大小。
在开始时,因为还不清楚每个人家族的情况,因此对于每个人,家族大小都初始化为1。
合并时,不只要改变祖先,a家族并入b家族时,b家族的大小也应该改变。
这就是点带权并查集。
以下为代码:
#include<bits/stdc++.h>
#define TIE ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
using namespace std;
const int N=1e5+5;
int n,m,a,b,f[N],nm[N];string s;
void init(){
for(int i=1;i<=n;i++){f[i]=i;nm[i]=1;}
}
int fd(int x){
if(f[x]==x)return x;return f[x]=fd(f[x]);
}
void Union(int x,int y){
int fx=fd(x),fy=fd(y);
if(fx==fy)return ;f[fx]=fy;nm[fy]+=nm[fx];
}
signed main(){
TIE;cin>>n>>m;init();
while(m--){
cin>>s>>a;
if(s=="Q2")cout<<nm[fd(a)]<<"\n";
else if(s=="Q1"){
cin>>b;
if(fd(a)==fd(b))cout<<"Yes\n";
else cout<<"No\n";
}else{cin>>b;Union(a,b);}
}
return 0;
}
边带权并查集
我们尝试用并查集来做这个题目。
首先可以建图,每个人跟他的传递对象建立关系,当出现环时说明游戏结束。
因此我们需要求出最小环的大小。
对于两个节点,如果他们处于同一个并查集中,但他们父亲节点却不相同,则说明他们在一个环内。
因为问的是大小,所以我们还需要直到环的大小。
记录路径长度,可以在递归寻找祖先时,开一个新的数组记录到根节点的长度。
代码丢了
这个题可以看出,并查集可以用于判断图是否有环。