数据结构-并查集

数据结构-并查集

非常基础简单有用的数据结构,可以合并两个集合、查询两个数是否在同一个集合,时间复杂度较玄学。

讲解:

在并查集中每个集合中的点之间的结构像一棵树,数组 f [ ] f[] f[] 表示一个节点的上司(父亲节点),如果 f [ x ] = = x f[x]==x f[x]==x 表示 x x x 是自己集合中的老板(根节点)。刚开始时每个人是一个集合,所以 f [ x ] = x f[x]=x f[x]=x

void fset(int n){// 初始化
	for(int i=1;i<=n;i++) f[i]=i;
}

因为 f [ x ] f[x] f[x] 只是表示 x x x 的上司,所以要找 x x x 的老板就是找 x x x 的上司的上司的上司的……直到那个上司 f [ f a ] = = f a f[fa]==fa f[fa]==fa

int find(int x){//找x的上司
	if(f[x]==x) return x;
	return f[x]=find(f[x]);
	//路径压缩,即让x的老板成为x的上司,这样以后找老板就方便了
}

合并 x x x y y y 所在的集合,只需要让 x x x 的老板的上司为 y y y 的老板。代码如下:

void merge(int x,int y){
	f[find(x)]=find(y);
}

判断 x x x y y y 是否在同一个集合内,只需要看 x x x 的老板是否等于 y y y 的老板,如下:

void magnet(int x,int y){
	if(find(x)==find(y)) puts("Y");
	else puts("N");
}

如果你懂了,蒟蒻就放总代码了:

#include <bits/stdc++.h>
using namespace std;
const int N=1e4+10;
class Mergeset{
public:int f[N];
	void fset(int n){
		for(int i=1;i<=n;i++) f[i]=i;
	}
	int find(int x){
		if(f[x]==x) return x;
		return f[x]=find(f[x]);
	}
	void merge(int x,int y){
		f[find(x)]=find(y);
	}
	void magnet(int x,int y){
		if(find(x)==find(y)) puts("Y");
		else puts("N");
	}
}s;
int n,m;
int main(){
	scanf("%d%d",&n,&m);
	s.fset(n);
	for(int i=1,x,y,z;i<=m;i++){
		scanf("%d%d%d",&x,&y,&z);
		if(x==1) s.merge(y,z);
		else if(x==2) s.magnet(y,z);
	}
	return 0;
}

一次 f i n d ( ) find() find() 的时间复杂度为 Θ ( α ( n ) ) \Theta(\alpha(n)) Θ(α(n)),其中 α ( n ) < log ⁡ n \alpha(n)<\log n α(n)<logn

普通并查集讲完后,接下来是种类并查集并查集可以维护关系的传递性和连通性,普通并查集的原理是“朋友的朋友是朋友”或“上司的上司是上司”,而种类并查集的原理是“敌人的敌人是朋友”或者更复杂。

例题:[NOIp提高组]关押罪犯

即给你 n n n 个点, m m m 条边,让你把所有点分成两份,使两端在同一份点的边的边权最大值最小。

讲解:

这题中输入每对罪犯关系以后,根据贪心思想,对每个关系按影响从大到小排序。如下:

scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
	scanf("%d%d%d",&em[i].a,&em[i].b,&em[i].h);
sort(em+1,em+m+1,cmp);

x = 1 ∼ n x=1\sim n x=1n 的节点表示罪犯 x x x 在第一个监狱,用 x = n + 1 ∼ 2 n x=n+1\sim 2n x=n+12n 的节点表示罪犯 x − n x-n xn 在第二个监狱。两个节点在同一个集合内表示两种情况一起发生。

刚开始时所有 2 n 2n 2n 个节点自己为一个集合(记得初始化!),然后枚举排序过后的关系,如果两个罪犯已经在同个监狱,答案就为两个罪犯之间的仇恨值。否则,把两个罪犯分在不同的监狱。如下:

for(int i=1;i<=m;i++){
	if(s.magnet(em[i].a,em[i].b)||s.magnet(em[i].a+n,em[i].b+n))
		{ans=em[i].h;break;} //如果两个罪犯已经在同个监狱
	s.merge(em[i].a+n,em[i].b);
	s.merge(em[i].a,em[i].b+n); //分到不同监狱
}

如果你懂了,蒟蒻就放代码了:

#include <bits/stdc++.h>
using namespace std;
const int N=4e4+10,M=1e5+10;
int n,m,ans;
class Em{public:int a,b,h;}em[M];
bool cmp(Em x,Em y){return x.h>y.h;}
class Mergeset{
public:int f[N];
	void fset(int x){
		for(int i=1;i<=x;i++) f[i]=i;
	}
	int find(int x){
		if(f[x]==x) return x;
		return f[x]=find(f[x]);
	}
	void merge(int x,int y){
		f[find(x)]=find(y);
	}
	bool magnet(int x,int y){
		return find(x)==find(y);
	}
}s;
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
		scanf("%d%d%d",&em[i].a,&em[i].b,&em[i].h);
	sort(em+1,em+m+1,cmp);
	s.fset(2*n);
	for(int i=1;i<=m;i++){
		if(s.magnet(em[i].a,em[i].b)||s.magnet(em[i].a+n,em[i].b+n))
			{ans=em[i].h;break;}
		s.merge(em[i].a+n,em[i].b);
		s.merge(em[i].a,em[i].b+n);
	}
	printf("%d\n",ans);
	return 0;
}

种类并查集相关题目推荐:[NOI2001]食物链
并查集的后续知识有可持久化并查集

祝大家学习愉快!

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值