并 查 集

并查集介绍

将两个集合合并
询问两个元素是否属于同一个集合
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 1n,m105
输入样例:

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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值