文章目录
一、并查集定义
1 有一道题
1.1 题目描述
若某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,现在给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系。
规定: x x x 和 y y y 是亲戚, y y y 和 z z z 是亲戚,那么 x x x 和 z z z 也是亲戚。如果 x x x, y y y 是亲戚,那么 x x x 的亲戚都是 y y y 的亲戚, y y y 的亲戚也都是 x x x 的亲戚。
1.2 思路
我们当然可以用深搜解决。但是,请注意: n , m , p ≤ 5000 n,m,p \le 5000 n,m,p≤5000
时间复杂度达到了百万级别,会!炸!
所以,并查集就出来了。
2 使用
2.1 查找
使用路径压缩,我们可以使原本百万级的时间复杂度变成均摊 O ( 1 ) O(1) O(1) 。首先讲一个故事:
假设有一池子鱼,每条鱼都只知道自己是自己的祖先,不知道任何别的鱼。
程序化就是:
void init(){
for(int i=1;i<=n;i++){
f[i]=i;//f数组存储祖先
}
}
有一天,所有鱼都知道自己的爹、自己爹的爹…是谁了(没有祖先的只知道自己)。现在我们想知道两条鱼是否在同一家族中(是否在同一集合),也就是想知道它俩是否具有共同祖先。
所以,我们让所有鱼都只知道它最原始的祖先即可。如果祖先相同,就代表在同一家族中。
写成函数如下:
int Find(int x){
if(f[x]==x){
//如果自己是自己的祖先,就找到头了
return x;
}
return f[x]=Find(f[x]);//路径压缩
}
这便是并查集的查找。
2.2 合并
给定两个元素,将这两个元素所在的集合合并成一个集合,合并可以将一个集合的祖先设为另一个集合的祖先的儿子。
参考代码如下:
void merge(int x,int y){
x=Find(x);
y=Find(y);
f[x]=y;
}
2.3 作用
可以通过并查集判环。
Q:什么时候会出现环呢?
A:两个处在一个集合内的点要合并时会出现。
2 回到题目
通过并查集,我们就可以轻易秒掉这道题了。
#include<bits/stdc++.h>
#define ll long long
#define bug printf("---OK---")
#define pa printf("A: ")
#define pr printf("\n")
#define pi acos(-1.0)
#define INF 0x3f3f3f3f3f3f3f3f
using namespace std;
const ll N=2e5+5;
ll f[N],n,m,p;
void init(){
for(int i=1;i<=n;i++){
f[i]=i;
}
}
ll Find(ll x){
if(f[x]==x){
return x;
}
return f[x]=Find(f[x]);
}
void merge(ll x,ll y){
x=Find(x);
y=Find(y);
f[x]=y;
}
int main(){
cin>>n>>m>>p;
init();
for(int i=1;i<=m;i++){
ll x,y;
cin>>x>>y;
merge(x,y);
}
while(p--){
ll x,y;
cin>>x>>y;
ll a=Find(x),b=Find(y);
if(a==b){
cout<<"Yes";
}
else{
cout<<"No";
}pr;
}
return 0;
}