GDUT ACM2022寒假集训 专题三 B C(并查集)

更好的阅读体验请前往:Paxton的小破站

一、并查集

并查集是一种树型的数据结构,用于处理一些不相交集合(disjoint sets)的合并及查询问题。——百度百科

并查集有三个操作

  • 合并(Union):把两个不相交的集合合并为一个集合。
  • 查询(Find):查询两个元素是否在同一个集合中。
  • 初始化(init):使用并查集前将并查集数组初始化为自身

初始化(init)

void init(int n)
{
	for (int i = 1; i <= n; ++i)
	{
		fa[i] = i;//将并查集数组初始化为自身
	}
	return;
}

查询(Find)

int find(int x)
{
	if (x == fa[x])//查询的节点自身为祖先
		return x;//返回自身
	else
		return fa[x] = find(fa[x]);//使用递归操作在查询节点祖先时直接将数值改为祖先,减少查询量
}

合并(Union)

void unionn(int i, int j)
{
	int i_fa = find(i);
	int j_fa = find(j);
	fa[i_fa] = j_fa;//将节点i的父节点设置为j
	return;
}

H71UFs.md.jpg
H78S81.md.jpg

例题一(Learning Languages)

原题链接:https://vjudge.net/contest/478160#problem/B

1、题干

BerCorp公司有n名雇员。这些雇员共掌握m种官方语言(以从1到m的整数编号)用于正式交流。对于每个雇员,我们有一个他掌握的语言列表,列表可以为空,这意味着一个雇员可能不掌握任何官方语言。但是雇员们愿意学习语言,只要公司为课程付费。每名雇员学习一种语言需要花费 1 Ber元。

请找出能让所有雇员直接或间接(可由其他雇员提供中间翻译)交流的最小花费。

2、输入格式

第一行为两个整数n,m(2<=n,m<=100),为雇员的数量和语言的数量。

接下来n行,每行首先有一个整数ki(0<=ki<=m),为雇员i掌握的语言数量,接下来有ki个整数,为雇员i掌握的语言。这意味着一个表中所有的编号都不同。注意一个雇员可能掌握0种语言。

每行中的数字都用一个空格隔开。

3、输出格式

一个整数——能让所有雇员直接或间接交流的最小花费。

注:(题目翻译来自洛谷)

4、样例

sample input 1

5 5
1 2
2 2 3
2 3 4
2 4 5
1 5

sample output 1

0

sample input 2

8 7
0
3 1 2 3
1 1
2 5 4
2 6 7
1 3
2 7 4
1 1

sample output 2

2

sample input 3

2 2
1 2
0

sample output 3

1

例题一题解

1、分析

并查集基础题,我们可以将n个人设为fa数组的前n位,m种语言设置为数组的n+m位

雇员 i 掌握第 j 门语言就直接使用union( i , j )连接,查询时只要雇员有共同的祖先他们就可以进行交流

因此最后只需要看n个人中有多少个不一样的祖先就表示有多少个无法互相交流的人群

输出该数-1即为答案

注意所有人都不会说话的情况,需要学习n门语言,特判

2、代码

#include <iostream>
using namespace std;

int fa[205];
bool vis[205];

void init(int n)
{
	for (int i = 1; i <= n; ++i)
	{
		fa[i] = i;
	}
	return;
}

int find(int x)
{
	if (x == fa[x])
		return x;
	else
	{
		fa[x] = find(fa[x]);
		return fa[x];
	}
}

void unionn(int i, int j)
{
	int i_fa = find(i);
	int j_fa = find(j);
	fa[i_fa] = j_fa;
	return;
}

int main()
{
	int n, m;
	cin >> n >> m;
	init(n + m);//初始化
	int temp = 0;
	for (int i = 1; i <= n; ++i)
	{
		int k;
		cin >> k;
		if (k == 0)
			temp++;//用于记录是否全部不会说话
		while (k--)
		{
			int a;
			cin >> a;
			unionn(i, a + n);//将人与语言连接
		}
	}
	int num = 0;
	for (int i = 1; i <= n; ++i)
	{
		fa[i] = find(i);//进行一次查询将所有节点的数字更新为祖先
	}
	for (int i = 1; i <= n; ++i)
	{
		if(vis[fa[i]]==0)//寻找有多少不同祖先
		{
			vis[fa[i]] = 1;
			num++;
		}
	}
	if(temp==n)
	{
		cout << n;//所有人不会说话
	}
	else	
		cout << num-1;
	return 0;
}

例题二(Equals)

原题链接:https://vjudge.net/contest/478160#problem/D

1、题干

给一个 1 到 N 的整数排列:p1,p2…pn

再给出 M 对整数对 (x1,y1),(x2,y2)… (xM,yM) ,其中∀i,1⩽xi ,yi⩽N

每对整数对对应一个操作,第 ii 对整数对对应着操作:将排列中第 xi个数和第 yi个数交换。

现在 AtCoDeer 希望通过执行任意次交换操作,使得 pi=i的数量尽可能多。

求出按任意顺序执行任意次操作后,最多能使多少 pi=i

注:(翻译来源洛谷)

2、样例

sample input 1

5 2
5 3 1 4 2
1 3
5 4

sample output 1

2

sample input 2

3 2
3 2 1
1 2
2 3

sample output 2

3

sample input 3

10 8
5 3 6 8 7 10 9 1 2 4
3 1
4 1
5 9
2 5
6 5
3 5
8 9
7 9

sample output 3

8

例题二题解

1、分析

用fa数组将相互交换的点位连接

由于操作可以执行任意次,把操作看成一条边,如果两个点在同一连通块内就可以无其他影响地互换。

2、代码

#include <iostream>
using namespace std;
int fa[100005],p[100005];

int find(int x)
{
	if(fa[x]==x)
		return x;
	else
	{
		fa[x] = find(fa[x]);
		return fa[x];
	}
}

void unionn(int i,int j)
{
	int i_fa = find(i);
	int j_fa = find(j);
	fa[i_fa] = j_fa;
	return;
}

int main()
{
	int n, m;
	cin >> n >> m;
	for (int i = 1; i <= n;++i)
	{
		cin >> p[i];
	}
	for (int i = 1; i <= n;++i)
	{
		fa[i] = i;
	}
	for (int i = 1; i <= m;++i)
	{
		int x, y;
		cin >> x >> y;
		fa[find(x)] = find(y);//将能够相互交换的点位连接并更新为祖先
	}
	int num = 0;
	for (int i = 1; i <= n;++i)
	{
		if(find(p[i])==find(i))
		{
			num++;
		}
	}
	cout << num;
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值