并查集介绍
将两个集合合并
询问两个元素是否属于同一个集合
b
e
l
o
n
g
[
x
]
=
a
belong[x]=a
belong[x]=a
i
f
(
b
e
l
o
n
g
[
x
]
=
=
b
e
l
o
n
g
[
y
]
)
if(belong[x]==belong[y])
if(belong[x]==belong[y])
合并两个集合,集合a 2000个元素,集合b 1000000个元素,要么把集合a的元素的集合编号全部改为b,要么把集合b的元素的集合编号全部改为a,复杂耗时
并查集可以以近乎O(1)的复杂度实现以上两个操作
每一个集合都用树的形式维护
把一整棵树插到另一棵树的某个部分
优化:
路径压缩:
一旦找到根节点,把所有节点的父节点都改为根节点
只需要搜一遍,一旦找到了根节点,就把所有的节点都指向根节点
比如第一次叶子节点找到根节点(集合编号)要走n步,第二次查询该叶子节点的集合编号就只需要一步了
按秩合并?一般不用
836. 合并集合(板子)
一共有 n 个数,编号是 1∼n,最开始每个数各自在一个集合中。
现在要进行 m 个操作,操作共有两种:
M a b,将编号为 a 和 b 的两个数所在的集合合并,如果两个数已经在同一个集合中,则忽略这个操作;
Q a b,询问编号为 a 和 b 的两个数是否在同一个集合中;
输入格式
第一行输入整数 n 和 m。
接下来 m 行,每行包含一个操作指令,指令为 M a b 或 Q a b 中的一种。
输出格式
对于每个询问指令 Q a b,都要输出一个结果,如果 a 和 b 在同一集合内,则输出 Yes,否则输出 No。
每个结果占一行。
数据范围
1
≤
n
,
m
≤
1
0
5
1≤n,m≤10^5
1≤n,m≤105
输入样例:
5 10
M 2 3
Q 2 3
M 1 5
Q 3 2
M 1 4
Q 4 5
Q 1 2
Q 2 4
M 4 1
Q 5 3
输出样例:
Yes
Yes
Yes
No
No
No
#include <iostream>
using namespace std;
const int N=1e5+5;
int p[N];
int find(int x){
// int temp=x;
// while(x!=p[x])x=p[x];
// p[temp]=x;//路径压缩
// return x;
if(x!=p[x])p[x]=find(p[x]);
return p[x];
//if(x!=p[x])find(p[x]);为了路径压缩要x的父节点p[x]
// 要变成根节点find(…find(p[x]))不断递归得到的根节点
}
int main(){
int n,m;
cin>>n>>m;
char op;
int x,y;
for(int i=1;i<=n;i++){
p[i]=i;
}
while(m--){
cin>>op>>x>>y;
if(op=='M'){
p[find(x)]=find(y); //x的根节点认y的根节点做父亲
}
else if(op=='Q'){
if(find(x)==find(y))cout<<"Yes";
else cout<<"No";
if(m)cout<<endl;
}
}
return 0;
}
837. 连通块中点的数量
相较于上一题多了一个查询集合中元素的操作
每个集合都是数,只有根节点的size才是有意义的
只保证根节点的size是有意义的就行
进行并查集操作的时候同时维护每个集合中元素的数量
#include <iostream>
using namespace std;
const int N=1e5+5;
int fa[N];
int siz[N];//只要维护每个集合中根节点的size有意义就ok
int find(int x){
if(x==fa[x])return x;
else return fa[x]=find(fa[x]);
}
int main(){//17:42
int n,m;
cin>>n>>m;//n个顶点
for(int i=1;i<=n;i++){
fa[i]=i;
siz[i]=1;
}
string op;
int a,b;
while(m--){
cin>>op;
if(op=="C"){
cin>>a>>b;
int x=find(a);
int y=find(b);
if(x!=y){
fa[x]=y;
siz[y]+=siz[x];
}
}
else if(op=="Q1"){
cin>>a>>b;
int x=find(a);
int y=find(b);
if(x==y)cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
else if(op=="Q2"){
cin>>a;
int x=find(a);
cout<<siz[x]<<endl;
}
}
return 0;
}
***用并查集维护额外信息
并查集维护各个节点到根节点的距离
只要直到两个元素的关系就把他放到同一个集合中去
不管是同类还是捕食关系
这样一个集合里面的关系就可以推理出来
比如 A和B是同类, B吃C,那么A、B、C相互之间的关系都可以推理出来
A吃B,B吃C,就可以推出C吃A
只要直到每个点和根节点之间的关系,就可以确定任意节点之间的关系
用每个点到根节点之间的距离来表示它和根节点之间的关系
并查集维护各个节点到根节点的距离
因为各种动物之间的关系构成一个一个的环,有环——求余
A吃B,B吃C,C吃D,D吃E
假设A为根节点
B到A距离为1, 根节点吃你
C到B距离为1,C到A距离为2 ,你吃根节点
D到C距离为1,D到B距离为2,D到A距离为3,根节点与你同类
E到D距离为1,E到C距离为2,E到B距离为3,E到A距离为4(4%3==1) 根节点吃你
各个节点到根节点的距离 的含义如上所示,
可以解释为
A吃B,B吃C,C吃D,D吃E
则
A是第0代
B是第1代
C是第2代
D是第3代
E是第4代
当然了,由于op为2时给出的语句是 x吃y,还是让x成为子节点,y成为根节点,字节点吃根节点,第1代吃第0代,第2代吃第1代,第3代吃第2代,这样就以子节点为吃的主体了
路径压缩时,把每个节点到父节点的距离更新成到他根节点的距离(对于每个节点只能知道它到他父节点的距离)
有n个人,要知道任意n个人之间的等级关系,要存
n
2
n^2
n2对关系,但只要清楚它和它的领袖之间的等级关系,
#include <iostream>
using namespace std;
const int N=50005;
int fa[N];
int d[N];//记录各个顶点到所在集合中根节点的距离
/*int find(int x){//整个的主题都是x节点
if(x!=fa[x]){
//x到根节点的距离等于x到其父节点的距离+父节点到根节点的距离
// 其中 父节点到根节点的距离 也是以一个递归的定义,同上
int dis=d[fa[x]]; //先记录x的父节点到根节点的距离,因为fa[x]要变成根节点了
fa[x]=find(fa[x]);//路径压缩,x认根节点做父节点
d[x]=d[x]+dis;
}
return fa[x];
}*/
int find(int x){//整个的主题都是x节点
if(x!=fa[x]){
// int dis=d[fa[x]];
// fa[x]=find(fa[x]);
// d[x]=d[x]+dis;
//x到根节点的距离等于x到其父节点的距离+父节点到根节点的距离
// 其中 父节点到根节点的距离 也是以一个递归的定义,同上
都说了是个递归的定义了,需要先完成所有的递归(递归会将到一路上根节点的所有孩子节点的的父亲都变成根节点,不只是x节点,同时所有节点的d[x]也会更新成到根节点的距离
而上面的代码d[x]=d[x]+dis;dis取的是尚未递归前的d[fa[x]],只是x的父节点到它的父节点的一段距离而已,递归后d[fa[x]]才会变成fa[x]到根节点的距离
int temp=find(fa[x]);
d[x]+=d[fa[x]];
fa[x]=temp;
}
return fa[x];
}
int main(){
int n,k;
cin>>n>>k;
for(int i=1;i<=n;i++){
fa[i]=i;
d[i]=0;
}
int cnt=0;
int op;
int x,y;
while(k--){
cin>>op>>x>>y;
if(x>n||x<1||y>n||y<1){
cnt++;
continue;
}
else{
int fx=find(x);
int fy=find(y);
if(op==1){//同类,距离%3==0,
if(fx==fy){
if((d[x]%3)!=(d[y]%3))cnt++;//(d[x]-d[y])%3==0
}
else{//如果不在一个集合,肯定无法直到x,y关系
fa[fx]=fy;
// x到根节点fy距离= x到根节点fx距离 + fx到根节点fy距离
// x到根节点fy距离%3 == y到根节点fy距离%3, 即模3同余
// (x到根节点fy距离-y到根节点fy距离)%3==0
d[fx]=d[y]-d[x] ;
}
}
else if(op==2){//x吃y
// x到根节点fy距离%3 == y到根节点fy距离%3 +1, 即模3下x=y+1
if(fx==fy&&(d[x]-d[y]-1)%3!=0)cnt++;
else if(fx!=fy){
fa[fx]=fy;
// (d[x]+d[fx]-d[y]-1)%3=0
d[fx]=d[y]+1-d[x];
}
}
}
}
cout<<cnt;
return 0;
}