并查集
作用:
1.将两个集合合并
2.询问两个元素是否在一个集合当中
暴力做法:O(n)
belong[x]=x
1.把belong[x]=a改为b O(n)
2.belong[x1]==belong[x2] O(1)
而用并查集复杂度:近乎O(1)
每个集合以树的形式维护’
每个集合编号是它根结点的标号
每个点都存储其父结点标号,就能通过其父结点编号快速找到该结点是属于哪个集合的
p[x]表示x的父结点
问题1:
如何判断某一结点是不是根结点
if(p[x]]==x)
问题2:
如何求x的集合编号
向上找其根结点
while(p[x]!=x){
x=p[x];
}
问题3:
如何合并两个结合·
将一个集合的根结点作为另一个集合的根结点的儿子
px为集合x的编号,py是集合y的编号
合并,令
p[x]=y;
优化:(路径压缩)
找到根节点后,让路径上所有点都直接指向根结点,从而进行路径压缩,减少搜索的长路
注意:scanf读字符容易读进空格和回车,scanf读字符串,自动忽略空格和回车
要用scanf读入一个字符建议用字符串的形式
例题:合并集合
#include <iostream>
using namespace std;
const int N=100100;
int p[N];
int find(int x){
if(p[x]!=x)p[x]=find(p[x]);
return p[x];
}
int main(){
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)p[i]=i;
for(int i=0;i<m;i++){
char op[2];
int a,b;
scanf("%s%d%d",op,&a,&b);
if(op[0]=='M'){
p[find(a)]=find(b);
}else {
if(find(a)==find(b)){
puts("Yes");
}else{
puts("No");
}
}
}
return 0;
}
连通块中点的数量
并查集当前集合中点的个数–维护额外变量
可能存在自环和重边
只保证根结点的size有意义
#include <iostream>
using namespace std;
const int N=100100;
int p[N],s[N];
int find(int x){
if(p[x]!=x)p[x]=find(p[x]);
return p[x];
}
int main(){
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
p[i]=i;
s[i]=1;
}
for(int i=0;i<m;i++){
char op[5];
int a,b;
scanf("%s",op);
if(op[0]=='C'){
scanf("%d%d",&a,&b);
if(find(a)==find(b))continue;
s[find(a)]+=s[find(b)];
p[find(b)]=find(a);
}else if(op[1]=='1'){
scanf("%d%d",&a,&b);
if(find(a)==find(b))puts("Yes");
else puts("No");
}else{
scanf("%d",&a);
printf("%d\n",s[find(a)]) ;
}
}
return 0;
}
食物链
#include <iostream>
using namespace std;
const int N=5e4+10,M=3;
int p[N];
int cnt;
int d[N];//当前节点到父节点的距离
int find(int x){
if(p[x]!=x){
int pre=p[x];
p[x]=find(p[x]);
d[x]=(d[x]+d[pre])%M;
}
return p[x];
}
void D(int a,int b,int n){
int p1=find(a);
int p2=find(b);
if(p1==p2){
if(d[a]%M!=(d[b]+n)%M)cnt++;
return;
}
p[p2]=p1;
d[p2]=((d[a]-d[b]-n)+M)%M;
}
int main(){
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
p[i]=i;
d[i]=0;
}
while(m--){
int op,a,b;
scanf("%d%d%d",&op,&a,&b);
if(a>n||b>n){
cnt++;
continue;
}
D(a,b,op-1);
}
printf("%d",cnt);
return 0;
}