P8654 [蓝桥杯 2017 国 C] 合根植物 - 连通世界大冒险 题解
🌱 P8654 [蓝桥杯 2017 国 C]合根植物 - 连通世界大冒险 题解
题目链接
题目描述
在一个 m m m 行 n n n 列的网格中种植了 m × n m \times n m×n 株植物。植物会与相邻植物"连根"(形成连通块),连通的植物群称为"合根植物"。现给出 k k k 对连根关系,求最终合根植物的数量。
输入格式
- 第一行: m m m 和 n n n(网格行数和列数)
- 第二行: k k k(连根关系数量)
- 接下来 k k k 行:每行两个整数 a a a 和 b b b,表示植物 a a a 和 b b b 连根
输出格式
一个整数,表示合根植物的数量
算法思路
核心思想
使用并查集管理植物的连通关系:
- 初始化:每株植物都是独立连通块
- 合并操作:对每对连根植物进行合并
- 计数优化:每次成功合并,连通块数减1
- 最终结果:剩余连通块数即为合根植物数
关键步骤
- 问题转化:将二维网格映射为一维编号(植物编号从1到 m × n m \times n m×n)
- 并查集操作:
find:带路径压缩的根节点查找unite:合并两个连通块(合并成功时计数减1)
- 连通块计数:初始计数为 m × n m \times n m×n,每次有效合并减1
复杂度分析
- 时间复杂度:
O
(
k
α
(
m
n
)
)
O(k \alpha(mn))
O(kα(mn))
- α \alpha α 为反阿克曼函数(接近 O ( 1 ) O(1) O(1))
- k k k 为连根关系数
- 空间复杂度: O ( m n ) O(mn) O(mn)
代码实现
#include <iostream>
#include <vector>
using namespace std;
// 并查集类
class UnionFind {
private:
vector<int> parent; // 父节点数组
int count; // 连通块计数器
public:
// 初始化并查集
UnionFind(int n) {
parent.resize(n + 1);
for (int i = 1; i <= n; i++) {
parent[i] = i; // 每个节点初始独立
}
count = n; // 初始连通块数 = 总节点数
}
// 查找根节点(带路径压缩)
int find(int x) {
return parent[x] == x ? x : parent[x] = find(parent[x]);
}
// 合并节点
void unite(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (rootX == rootY) return; // 已在同一连通块
parent[rootY] = rootX; // 合并
count--; // 连通块减少
}
// 获取连通块数量
int getCount() const {
return count;
}
};
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int m, n;
cin >> m >> n;
int total = m * n; // 总植物数
UnionFind uf(total); // 初始化并查集
int k;
cin >> k;
// 处理连根关系
while (k--) {
int a, b;
cin >> a >> b;
uf.unite(a, b); // 合并连通块
}
// 输出合根植物数量
cout << uf.getCount() << endl;
return 0;
}
代码解释
数据结构
- UnionFind类:封装并查集操作
parent:存储节点的父节点count:动态维护连通块数量
- 核心方法:
find(x):查找根节点(路径压缩优化)unite(x,y):合并两个节点(合并成功时计数减1)getCount():获取当前连通块数量
关键逻辑
-
初始化:
UnionFind uf(total); // 创建大小为total的并查集 -
合并操作:
uf.unite(a, b); // 合并植物a和b- 内部自动判断是否已连通
- 只有未连通时才合并并减少计数
-
结果输出:
uf.getCount(); // 最终连通块数
算法正确性证明
计数原理
设初始连通块数
C
0
=
m
n
C_0 = mn
C0=mn
对于每个有效合并操作:
- 当两个独立连通块合并时, C i = C i − 1 − 1 C_i = C_{i-1} - 1 Ci=Ci−1−1
- 当两个已连通块合并时, C i = C i − 1 C_i = C_{i-1} Ci=Ci−1
最终连通块数 C f i n a l C_{final} Cfinal 即为合根植物数
并查集优化
-
路径压缩:
parent[x] = find(parent[x]); // 压缩路径使树保持扁平,查询效率 O ( α ( n ) ) O(\alpha(n)) O(α(n))
-
动态计数:
if (rootX != rootY) count--;在合并时即时更新连通块数
测试样例
输入 #1
5 4
16
2 3
1 5
5 9
4 8
7 8
9 10
10 11
11 12
10 14
12 16
14 18
17 18
15 19
19 20
9 13
13 17
输出 #1
5
解释:20株植物经过16次合并后形成5个连通块:
- {1,5,9,10,11,12,13,14,16,17,18}(11株)
- {2,3}(2株)
- {4,7,8}(3株)
- {15,19,20}(3株)
- {6}(1株)
输入 #2
3 3
5
1 2
2 3
4 5
5 6
7 8
输出 #2
3
解释:9株植物形成3个连通块:
- {1,2,3}
- {4,5,6}
- {7,8,9}
复杂度证明
时间复杂度
设 N = m × n N = m \times n N=m×n(总植物数):
- 并查集初始化: O ( N ) O(N) O(N)
- k k k 次合并操作:每次 O ( α ( N ) ) O(\alpha(N)) O(α(N))
- 总时间: O ( N + k α ( N ) ) ≈ O ( k ) O(N + k \alpha(N)) \approx O(k) O(N+kα(N))≈O(k)(当 k ≫ N k \gg N k≫N 时)
空间复杂度
- 存储并查集: O ( N ) O(N) O(N)
- 总空间: O ( N ) O(N) O(N)
拓展思考
- 可视化连通块:可添加DFS/BFS遍历输出每个连通块
- 动态连根:支持在线查询和更新
- 区域统计:计算每个连通块的大小和位置
相似题目
更多推荐


所有评论(0)