题解:AcWing 836 合并集合
【题目来源】
AcWing:836 合并集合 - AcWing题库
【题目描述】
一共有 \(n\) 个数,编号是 \(1\sim 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。
每个结果占一行。
【输入样例】
4 5
M 1 2
M 3 4
Q 1 2
Q 1 3
Q 3 4
【输出样例】
Yes
No
Yes
【核心思想】
-
问题分析:给定 \(n\) 个初始各自独立的元素,进行 \(m\) 次合并或查询操作。需要高效判断两个元素是否属于同一集合,以及合并两个集合。关键观察是:用树形结构表示集合,每个集合选一个代表元(根节点),通过路径压缩和按秩合并优化查找和合并操作。
-
算法选择:
- 并查集(Disjoint Set Union, DSU):用父节点数组
p维护每个元素所在集合,支持 \(O(\alpha(n))\) 的查找和合并 - 路径压缩:查找时将访问过的所有节点直接连接到根节点,扁平化树结构
- 并查集(Disjoint Set Union, DSU):用父节点数组
-
关键步骤:
- 初始化:\(p[i] = i\)(每个元素自成一个集合,自己是自己的父节点)
- 查找
find(x):- 若 \(p[x] = x\),返回 \(x\)(\(x\) 是根节点)
- 否则 \(p[x] = \text{find}(p[x])\)(递归查找根节点,同时路径压缩)
- 返回 \(p[x]\)
- 合并
M a b:- \(p[\text{find}(a)] = \text{find}(b)\)(将 \(a\) 所在集合的根节点指向 \(b\) 所在集合的根节点)
- 查询
Q a b:- 若 \(\text{find}(a) = \text{find}(b)\),输出
Yes,否则输出No
- 若 \(\text{find}(a) = \text{find}(b)\),输出
-
时间/空间复杂度:
- 时间复杂度:\(O(m \cdot \alpha(n))\),其中 \(\alpha\) 为阿克曼函数的反函数,增长极慢,实际可视为常数
- 空间复杂度:\(O(n)\),父节点数组
-
并查集的核心思想:
- 代表元思想:每个集合用一个根节点作为代表,所有元素通过父节点链连接到根。判断两个元素是否同集合只需比较它们的根节点
- 路径压缩优化:查找过程中将访问路径上的所有节点直接挂到根节点下,使得后续对这些节点的查找变为 \(O(1)\)。这是一种均摊优化,不改变树的结构逻辑但大幅提升实际效率
- 合并的简洁性:合并只需修改一个指针(根节点的父节点),无需遍历整个集合。配合路径压缩,树的深度始终保持在极小范围
- 近乎常数的复杂度:阿克曼函数反函数 \(\alpha(n)\) 在 \(n < 10^{600}\) 时不超过 \(4\),因此并查集的操作可视为 \(O(1)\),是处理动态连通性问题的最优数据结构
- 适用于动态连通性判断、最小生成树(Kruskal)、连通分量计数、等价类划分等场景
【解题思路】

【算法标签】
并查集
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 100010; // 定义最大节点数
int n, m; // n: 节点数量, m: 操作次数
int p[N]; // 并查集父节点数组
// 查找x的根节点(带路径压缩优化)
int find(int x)
{
if (p[x] != x) // 如果当前节点不是根节点
p[x] = find(p[x]); // 路径压缩:将父节点设为根节点
return p[x]; // 返回根节点
}
int main()
{
scanf("%d%d", &n, &m); // 输入节点数和操作数
// 初始化并查集,每个节点自成一个集合
for (int i = 1; i <= n; i++)
p[i] = i;
while (m--) // 处理每个操作
{
char op[2]; // 操作类型
int a, b; // 操作参数
scanf("%s%d%d", op, &a, &b);
if (op[0] == 'M')
{
// 合并操作:将a所在集合合并到b所在集合
p[find(a)] = find(b);
}
else
{
// 查询操作:判断a和b是否在同一集合
if (find(a) == find(b))
puts("Yes");
else
puts("No");
}
}
return 0;
}
【运行结果】
4 5
M 1 2
M 3 4
Q 1 2
Yes
Q 1 3
No
Q 3 4
Yes
浙公网安备 33010602011771号