更好的阅读体验请前往: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;
}
例题一(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;
}