并查集

引入

一些问题中,会询问多个量的关系,并且量与量还有传递性,如果我们需要把两个量联系起来,就需要使用并查集了。并查集是靠维护树来实现的。我们知道,在树中,若X能到Y且Y能到Z,则X能到Z,也具有传递性。那么如果A与B有联系,就可以在AB之间建立一条边。在询问关系时就看AB两棵树的根节点相不相同即可。
但是会有一个问题,我们要频繁地查找根节点,十分耗时,所以需要做一个优化。有递归查找根节点时,找到根节点后回溯更新下面的点。
如图:
在这里插入图片描述
我们可以把它路径压缩成:
在这里插入图片描述
这样就可以节省大量时间了。

例题

2988: 擒贼先擒王
题目描述
快过年了,犯罪分子也开始为年终奖奋斗了。晓哼的家乡出现了多次抢劫事件。由于强盗人数过于庞大,作案频繁,警方想查清楚到底有几个犯罪团伙实在太不容易了,不过警察叔叔还是搜集到了一些线索,需要咱们帮忙分析一下:
现在有10个强盗。
1号强盗与2号强盗是同伙。
3号强盗与4号强盗是同伙。
5号强盗与2号强盗是同伙。
4号强盗与6号强盗是同伙。
2号强盗与6号强盗是同伙。
8号强盗与7号强盗是同伙。
9号强盗与7号强盗是同伙。
1号强盗与6号强盗是同伙。
2号强盗与4号强盗是同伙。
有一点需要注意,强盗同伙的同伙也是同伙。你能帮助警方查出有多少个独立的犯罪团伙吗?
输入
第一行n m,n表示强盗的人数,m表示警方搜集到的m条线索。

接下来的m行每一行有两个数 a b。表示强盗a和强盗b是同伙。

输出
有多少个独立的犯罪团伙
样例输入
10 9
1 2
3 4
5 2
4 6
2 6
8 7
9 7
1 6
2 4
样例输出
3

分析

满足传递性,可以用并查集实现。

代码

#include<bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define full(a,b) memset(a,b,sizeof a)
#define N 100000+5
#define M 2*N
int m,n,ans;
int fa[N];
void init()
{
	full(fa,-1);//一开始都是根节点 
}
int fi(int x)//找根节点 
{
	if(fa[x]==-1) return x;//找到根节点 
	return fa[x]=fi(fa[x]);//回溯更新 
}
void un(int u,int v)//合并函数 
{
	int x=fi(u),y=fi(v);
	if(x!=y) fa[x]=y;//已经在一棵树中,不用建边 
}
int main()
{
//	freopen("2988: 擒贼先擒王.in","r",stdin);
//	freopen("2988: 擒贼先擒王.out","w",stdout);
	init();
	scanf("%d%d",&n,&m);
	for(int i=1; i<=m; i++)
	{
		int a,b;
		scanf("%d%d",&a,&b);
		un(a,b);//建一条A到B的边 
	}
	for(int i=1; i<=n; i++)
		if(fa[i]==-1) ans++;//数树 
	printf("%d",ans);
	return 0;
}

二维并查集

例题

Alice和Bob玩了一个古老的游戏:首先画一个n*n的点阵(图4-22中n=3)

接着,他们两个轮流在相邻的点之间画上虚边和粗边:

在这里插入图片描述
直到围成一个封闭的圈(面积不必为1)为止,“封圈”的那个人就是赢家。因为棋盘实在是太大了(n<=200),他们的游戏实在是太长了!他们甚至在游戏中都不知道谁赢得了游戏。于是请你写一个程序,帮助他们计算他们是否结束了游戏?

输入
第一行为两个整数n和m。m表示一共画了m条线,每条线长度为1.

以后m行,每行首先由两个数字(x,y),代表了画线的起点坐标,接着用空格隔开一个字符,加入字符是“D”,则是向下连一条边,如果是“R”就是向右连一条边,长度为1。输入数据不会有重复的边且保证正确。

输出
输出一行:在第几步的时候结束。假如m步之后也没有结束,则输出一行“draw”。

样例输入
3 5
1 1 D
1 1 R
1 2 D
2 1 R
2 2 D
样例输出
4

代码

#include<bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define full(a,b) memset(a,b,sizeof a)
#define N 1000+5
int m,n;
struct node
{
	int x,y;
} fa[N][N];
void init()
{
	full(fa,-1);
}
node fi(node x)
{
	node &sss=fa[x.x][x.y];
	if(sss.x==-1&&sss.y==-1) return x;
	return sss=fi(sss);
}
int main()
{
//	freopen("1606: 格子游戏.in","r",stdin);
//	freopen("1606: 格子游戏.out","w",stdout);
	init();
	scanf("%d%d",&n,&m);
	for(int i=1; i<=m; i++)
	{
		int a,b;char c;
		scanf("%d%d %c",&a,&b,&c);
		node x=fi(node{a,b}),y;
		if(c=='D') y=fi(node{a+1,b});
		else y=fi(node{a,b+1});
		if(x.x!=y.x||x.y!=y.y) fa[y.x][y.y]=x;
		else
		{
			printf("%d",i);
			return 0;
		}
	}
	printf("draw");
	return 0;
}

反集

并查集不仅可以判断在同一集合,还可以判断相对关系。只需要把原来的fa数组*2,fa[n+1~2n]表示前
[1 ~n]的相对点。

例题

P1892 [BOI2003]团伙

分析

朋友还是一般的并查集,但敌人呢?因为敌人的敌人是朋友,所以若A与B是敌人,则A与B+n是朋友,B与A+n是朋友。

代码

#include<bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define full(a,b) memset(a,b,sizeof a)
#define N 1000+5
int n,m,ans;
int fa[2*N];
void init()
{
	full(fa,-1);
}
int fi(int x)
{
	if(fa[x]==-1) return x;
	return fa[x]=fi(fa[x]);
}
void un(int x,int y)
{
	x=fi(x),y=fi(y);
	if(x!=y) fa[x]=y; 
}
int main()
{
//	freopen("1589: 团伙.in","r",stdin);
//	freopen("1589: 团伙.out","w",stdout);
	init();
	scanf("%d%d",&n,&m);
	for(int i=1; i<=m; i++)
	{
		char p;int a,b;
		scanf("\n%c%d%d",&p,&a,&b);
		if(p=='F')
		{
			un(a,b);
		}
		else
		{
			un(a+n,b);//!!!注意,是a+n指向b 
			un(b+n,a);
		}
	}
	for(int i=1; i<=n; i++)
		if(fa[i]==-1) ans++;
	printf("%d",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值