在平时写题的过程中,我们可能遇到这样一种题目----对于一个无向连通图,我们要询问2个点是否是连通的。在这个时候,我们就需要一个叫做并查集的数据结构来解决这种问题。
有关并查集的基本概念,在CSDN中有大佬的博客写的非常通俗易懂,CSDN博客:【算法与数据结构】—— 并查集(作者:酱懵静)大家如果在之前从来没有听说过并查集这个概念,那么建议在看完有关博客后再通过本博客的题目进行练习巩固。
题目情景引入 Acwing 合并集合
一共有 n 个数,编号是 1∼n,最开始每个数各自在一个集合中。
现在要进行 m 个操作,操作共有两种:
M a b,将编号为 a 和 b 的两个数所在的集合合并,如果两个数已经在同一个集合中,则忽略这个操作;
Q a b,询问编号为 a 和 b 的两个数是否在同一个集合中;
输入格式
第一行输入整数 n 和 m。
接下来 m 行,每行包含一个操作指令,指令为 M a b 或 Q a b 中的一种。
输出格式
对于每个询问指令 Q a b,都要输出一个结果,如果 a 和 b 在同一集合内,则输出 Yes,否则输出 No。
每个结果占一行。
数据范围
1≤n,m≤1e5
输入样例:
4 5
M 1 2
M 3 4
Q 1 2
Q 1 3
Q 3 4
输出样例:
Yes
No
Yes
在这里可以告诉大家,这道题就是一道并查集模板题。所以说,这一道题的ac代码其实是要求背下来的。
#include<iostream>
#include<iomanip>
#include <utility>
#include<cstdio>
#include<string>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#include<map>
#include<stack>
#include<set>
#include<bitset>
#include<ctime>
#include<cstring>
#include<list>
#define ll long long
#define fi first
#define se second
using namespace std;
typedef pair<int, int> PII;
const int N = 100050;
int n, m;
int p[N];//i的父亲是p[i],i=p[i]的是祖宗
int find(int x)//查找x的祖宗与路径压缩
{
if (p[x] != x)p[x] = find(p[x]);//路径压缩代码,把这条路的所有后代变成祖宗的儿子
return p[x];
}
void solve()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)//初始化,每个人自己就是自己家族的祖宗
{
p[i] = i;
}
while (m--)
{
char t;
int a, b;
cin >> t>>a>>b;
if (t == 'M')
{
p[find(a)] = find(b);//给a的祖宗找个爹 ---> b的祖宗,那么a,b家族共同的祖先就是b了
}
else
{
if (find(a) == find(b))//如果祖宗相同
{
cout << "Yes" << endl;
}
else
{
cout << "No" << endl;
}
}
}
}
int main()
{
solve();
return 0;
}
如果了解了并查集的概念的话,上面这道题的代码理解起来应该是不困难的。如果对上面的代码无法理解的话欢迎在下方评论留言提问。
在解决了并查集的最基本的问题后,我们需要进一步了解并查集的应用。
情景引入 Acwing 连通块中点的数量
给定一个包含 n 个点(编号为 1∼n)的无向图,初始时图中没有边。
现在要进行 m 个操作,操作共有三种:
C a b,在点 a 和点 b 之间连一条边,a 和 b 可能相等;
Q1 a b,询问点 a 和点 b 是否在同一个连通块中,a 和 b 可能相等;
Q2 a,询问点 a 所在连通块中点的数量;
输入格式
第一行输入整数 n 和 m。
接下来 m 行,每行包含一个操作指令,指令为 C a b,Q1 a b 或 Q2 a 中的一种。
输出格式
对于每个询问指令 Q1 a b,如果 a 和 b 在同一个连通块中,则输出 Yes,否则输出 No。
对于每个询问指令 Q2 a,输出一个整数表示点 a 所在连通块中点的数量
每个结果占一行。
数据范围
1≤n,m≤105
输入样例:
5 5
C 1 2
Q1 1 2
Q2 1
C 2 5
Q2 5
输出样例:
Yes
2
3
不难发现,这一题比起上一题增加了一个对于连通块中点的个数的操作。对此,我们的解决办法是----额外定义一个sum数组,对于sum[i],只有当i是这个家族的祖宗的时候才有意义,此时sum[i]代表i家族的成员的数量。因此我们把上一题的代码稍作修改就可以完成这道题目了。
#include<iostream>
#include<iomanip>
#include <utility>
#include<cstdio>
#include<string>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#include<map>
#include<stack>
#include<set>
#include<bitset>
#include<ctime>
#include<cstring>
#include<list>
#define ll long long
#define fi first
#define se second
using namespace std;
typedef pair<int, int> PII;
const int N = 100050;
int n, m;
int p[N];
int sum[N];
/*int find(int x)
{
if (x==p[x])
{
return x;
}
else
return p[x] = find(p[x]);
}*/
int find(int x)
{
if (p[x] == x)return x;
else
return p[x] = find(p[x]);
}
void solve()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
{
p[i] = i;
sum[i] = 1;
}
while (m--)
{
string op;
cin >> op;
if (op == "C")
{
int a, b;
cin >> a>>b;
a = find(a);//首先用a和b的祖先来代替a,b
b = find(b);
if (a != b)//如果祖宗不同则代表不是一个家族,执行合并操作
{
p[a] = b;//让a家族的祖先认b家族的祖先做爹
sum[b] += sum[a];//然后b家族的成员数加上所有a家族的成员数就是合并后大家族的人数,此时大家族的祖宗是b家族的祖宗
}
}
else if (op == "Q1")
{
int a, b;
cin >> a >> b;
cout << (find(a) == find(b) ? "Yes" : "No") << endl;//祖宗不同就不是一个家族
}
else
{
int a, b;
cin >> a;
cout << sum[find(a)] << endl;//用find找到祖宗,再通过sum[祖宗编号]来求出家族的人数(连通块的点的个数)
}
}
}
int main()
{
solve();
return 0;
}
上面的两道题目都是并查集的最基础的题目,适合刚刚学完并查集概念的同学用来练手。这两道题目的ac代码也是值得背下来的。
作者:Avalon Demerzel,喜欢我的博客就点个赞吧,更多图论与数据结构知识点请见作者专栏《图论与数据结构》