【并查集 / 图论】蓝桥云课 - 魔法大陆的城市群(求无向图连通块数量)题解
1. 题目概述
- 题目大意 :给定 nnn 个城市和 mmm 条双向道路。如果两个城市之间可以通过道路直接或间接到达,则属于同一个"城市群"。求魔法大陆上一共有多少个城市群(即求无向图中的连通块数量)。
- 数据规模 :2≤n≤1052 \le n \le 10^52≤n≤105,1≤m≤2×1051 \le m \le 2 \times 10^51≤m≤2×105
2. 解题思路
本题是经典的无向图连通块计数问题 。由于数据规模 nnn 达到了 10510^5105,使用 并查集(Disjoint Set Union, DSU) 是最简洁且高效的解法。
-
核心思想:
- 初始时,将 nnn 个城市视为 nnn 个互相独立的城市群,设置答案计数器
ans = n。 - 顺序读入每条双向道路 (u,v)(u, v)(u,v),利用并查集查找 uuu 和 vvv 的根节点:
- 若根节点不同,说明这两个城市群之前不连通,通过当前道路合并两个集合,并将
ans减 1。 - 若根节点相同,说明它们早已连通,忽略该条边。
- 若根节点不同,说明这两个城市群之前不连通,通过当前道路合并两个集合,并将
- 处理完所有道路后,剩余的
ans即为最终的城市群数量。
- 初始时,将 nnn 个城市视为 nnn 个互相独立的城市群,设置答案计数器
-
为什么选择并查集而非 DFS:
- 无需显式建图:不需要构造邻接表保存边关系,边读入边处理,节省内存。
- 防爆栈 :数据规模 n=105n=10^5n=105 时,若图退化为一条长链,DFS 递归层数过深易引发栈溢出(Segmentation Fault)。并查集配合路径压缩则非常稳定。
3. C++ 源码
cpp
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
int parent[MAXN];
// 查找根节点(带路径压缩)
int find(int x) {
if (parent[x] == x) return x;
return parent[x] = find(parent[x]); // 路径压缩,扁平化树结构
}
int main() {
// 优化标准输入输出流
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m;
if (!(cin >> n >> m)) return 0;
// 1. 初始化:每个城市各自独立为一个集合
for (int i = 1; i <= n; i++) {
parent[i] = i;
}
int ans = n; // 初始连通块数量为 n
// 2. 依次处理每条边
for (int i = 0; i < m; i++) {
int u, v;
cin >> u >> v;
int rootU = find(u);
int rootV = find(v);
// 不属于同一连通块则进行合并,连通块总数 -1
if (rootU != rootV) {
parent[rootU] = rootV;
ans--;
}
}
// 3. 输出最终的连通块数量
cout << ans << "\n";
return 0;
}
4. 复杂度分析
- 时间复杂度 :O(n+m⋅α(n))O(n + m \cdot \alpha(n))O(n+m⋅α(n))。其中 α\alphaα 为阿克曼函数的反函数,近乎于常数 O(1)O(1)O(1)。即使在最坏情况下,总运算次数也在 10510^5105 级别,运行时间不超过 20ms。
- 空间复杂度 :O(n)O(n)O(n)。仅需开辟一个大小为 10510^5105 的
parent父节点数组,内存消耗小于 1MB。