并查集 & 扩展域并查集

引言

由于蒟蒻在各种平台上都没有找到扩展域并查集这玩意的人话解释,好不容易理解后遂记录之。
又由于之前的坑太多了只得把并查集的一起填了。

并查集

并查集很好理解,就是维护点与点之间的关系的。比如说有 \(n\) 个人,给定 \(m\) 组朋友关系,求 \(a\)\(b\) 是不是朋友。
由于朋友的朋友是朋友,所以最终这 \(n\) 个人就会形成许多个朋友集合。那么并查集要做的就是维护这些元素构成的集合。

具体地说,当我们收到一条朋友关系时,两人所处的集合之间就产生了一座“桥梁”。也就是说两个集合连在一起构成了一个大集合,所以我们只需要连接这两个集合即可。
至于要怎么连,以及如何判断两点是否处于同一个集合,可以考虑维护父亲关系数组。有了这个数组,很显然每个不同集合都会有一个不同的“根”。那么只需要每次连接集合时更新父亲关系即可。

一份简单的代码就出炉了。

int getfa(int u){
	if(fa[u]==u) return u;
	return getfa(fa[u]);
}

但是这样写太慢了容易被卡,遂有人发明了路径压缩按秩合并

  • 路径压缩:就是每次查询“根”的时候把树压成高度为二的一坨玩意,因为父亲关系本来就是为了方便找“根”,所以如果高度为二那么只用跳一次。可以想象一个根节点下面挂了一排叶子节点的情景。
int getfa(int u){
	if(fa[u]==u) return u;
	return fa[u]=getfa(fa[u]);
}
  • 按秩合并:可以选择按照该集合子树大小或者深度合并,每次把小的那坨挂到大的那坨的根上,显然更优(代码没写)。

那么整个代码就长这样(路径压缩):

#include<bits/stdc++.h>
using namespace std;
int fa[500005];
int getfa(int u){
	if(fa[u]==u) return u;
	return fa[u]=getfa(fa[u]);
}
int main(){
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) fa[i]=i;
	for(int i=1;i<=m;i++){
		int op,a,b;
		scanf("%d%d%d",&op,&a,&b);
		int af=getfa(a),bf=getfa(b);
		if(op==2){
			if(af!=bf) printf("N\n");
			else printf("Y\n");
		}
		else{
			if(af!=bf) fa[af]=bf;
		}
	} 
	return 0;
}

例题:

扩展域并查集

还是刚刚那个例子,但是多了一种关系:敌人关系。
两种关系一个并查集如何处理呢?

其实很简单:设 \(i\) 的第二节点为 \(n+i\)\(n\) 为人数),\(j\) 的第二节点为 \(n+j\)。那么如果给出的是一组朋友关系,就连接节点 \(i\) 和节点 \(j\);否则就连接节点 \(n+i\) 和节点 \(j\)、节点 \(i\) 和节点 \(n+j\)
好的恭喜你学会了扩展域并查集。

\(Code\)

#include<bits/stdc++.h>
using namespace std;
int fa[1000005];
int getfa(int u){
	if(fa[u]==u) return u;
	return fa[u]=getfa(fa[u]);
}
int main(){
	int n;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		int opt,u,v;
		scanf("%d%d%d",&opt,&u,&v);
		int afa1=getfa(a),bfa1=getfa(b);
		if(opt==1)
			if(afa1!=bfa1) fa[afa1]=bfa1;
		else if(opt==2){
			int afa2=getfa(n+a),bfa2=getfa(n+b);
			if(afa1!=bfa2) fa[afa1]=bfa2;
			if(afa2!=bfa1) fa[afa2]=bfa1;
		}
		else
			if(afa1!=bfa1) printf("N\n");
			else printf("Y\n");
	}
	return 0;
}

免责声明:不知道对错与否,没测过。

posted @ 2026-03-26 21:29  404-NoT_FinD  阅读(48)  评论(0)    收藏  举报