题解:洛谷 P2919 Guarding the Farm
【题目来源】
洛谷:P2919 [USACO08NOV] Guarding the Farm S - 洛谷
【题目描述】
农场有许多小山丘,约翰农夫希望在这些小山丘上放置守卫,以确保他珍贵的奶牛的安全。
他想知道如果他希望在每个小山丘的顶部放置一个守卫,他需要多少守卫。他有一张地图,这张地图是一个整数矩阵;矩阵有 \(N\) 行(\(1 < N \leq 700\))和 \(M\) 列(\(1 < M \leq 700\))。矩阵中的每个元素表示一个高度 \(H_{ij}\)(\(0 \leq H_{ij} \leq 10,000\))。请帮助他确定地图上有多少个山顶。
一个山顶是由一个或多个相邻且具有相同值的矩阵元素组成,这些元素被地图的边缘或具有较低(较小)高度的元素完全包围。如果两个不同的元素的 \(X\) 坐标差的绝对值不大于 1,且 \(Y\) 坐标差的绝对值也不大于 1,则它们是相邻的。
【输入】
* 第 1 行:两个用空格分隔的整数:\(N\) 和 \(M\)
* 第 2 行到第 \(N+1\) 行:第 \(i+1\) 行描述矩阵的第 \(i\) 行,包含 \(M\) 个用空格分隔的整数:\(H_{ij}\)
【输出】
* 第 1 行:一个整数,表示山顶的数量
【输入样例】
8 7
4 3 2 2 1 0 1
3 3 3 2 1 0 1
2 2 2 2 1 0 0
2 1 1 1 1 0 0
1 1 0 0 0 1 0
0 0 0 1 1 1 0
0 1 2 2 1 1 0
0 1 1 1 2 1 0
【输出样例】
3
【核心思想】
-
问题分析:给定 \(N \times M\) 的高度矩阵,求"山顶"数量。山顶定义:由相邻(八连通)且高度相同的格子组成的连通块,且该连通块被边缘或更低高度的格子完全包围(即连通块内所有格子的八邻域中没有更高高度的格子)。这是一个BFS 找连通块 + 极值判断问题,关键在于对每个等高连通块判断其是否为局部最大值区域。
-
算法选择:
- BFS 找连通块:遍历每个未访问的格子,BFS 扩展八连通方向中高度相同的格子,标记访问
- 极值判断:在 BFS 过程中,检查连通块内每个格子的八邻域,若存在更高高度的格子,则该连通块不是山顶
-
关键步骤:
- 读入数据:\(N, M\) 和高度矩阵 \(h[1..N][1..M]\)
- 遍历所有格子(\(i\) 从 \(1\) 到 \(N\),\(j\) 从 \(1\) 到 \(M\)):
- 若
vis[i][j]已访问,跳过 bfs(i, j):找到以 \((i,j)\) 为起点的等高连通块val = h[i][j],记录当前连通块高度flag = true,标记是否为山顶- 队列扩展八邻域:
- 若
h[nx][ny] > val:flag = false(存在更高点,不是山顶) - 若
h[nx][ny] == val && !vis[nx][ny]:加入队列,标记访问
- 若
- 返回
flag
- 若
bfs返回true,ans++
- 若
- 输出
ans
-
时间/空间复杂度:
- 时间复杂度:\(O(N \cdot M)\),每个格子最多被访问一次,八邻域检查常数时间
- 空间复杂度:\(O(N \cdot M)\),高度矩阵和访问标记
-
BFS 连通块 + 极值判断的核心思想:
- 等高连通块:山顶不是单个格子,而是由相邻等高格子组成的区域,BFS 八连通扩展找到完整区域
- 局部最大值的区域化:传统"山顶"是单个最高点,本题扩展为"等高区域",要求整个区域的外围(八邻域)没有更高点
- 即时否定策略:BFS 过程中一旦发现某个连通块格子的邻域有更高点,立即置
flag = false,无需等待 BFS 结束 - 访问标记避免重复:每个格子只属于一个连通块,访问后不再处理,保证线性时间复杂度
- 适用于"网格连通块 + 局部极值判断"问题,核心在于将单点极值扩展为区域极值,用 BFS 统一处理
【算法标签】
普及+ #BFS-二维
【代码详解】
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII; // 定义坐标对类型
const int N = 705; // 定义地图最大尺寸
int n, m; // n: 行数,m: 列数
int h[N][N]; // 存储每个点的高度
bool vis[N][N]; // 标记每个点是否被访问过
// 八个方向的偏移量
int dx[8] = {-1, -1, -1, 0, 0, 1, 1, 1};
int dy[8] = {-1, 0, 1, -1, 1, -1, 0, 1};
// 广度优先搜索函数,判断(x,y)是否是山丘的最高点
bool bfs(int x, int y)
{
int val = h[x][y]; // 记录当前连通块的高度值
bool flag = true; // 标记当前连通块是否满足山丘条件
queue<PII> q; // 定义队列用于BFS
q.push({x, y}); // 将起点加入队列
vis[x][y] = true; // 标记起点已访问
while (!q.empty()) // 当队列不为空时循环
{
int xx = q.front().first, yy = q.front().second; // 取出队首坐标
q.pop(); // 弹出队首
for (int i = 0; i < 8; i++) // 遍历八个方向
{
int nx = xx + dx[i], ny = yy + dy[i]; // 计算相邻坐标
if (nx < 1 || nx > n || ny < 1 || ny > m) // 检查边界
continue;
if (h[nx][ny] > val) // 如果相邻点高度大于当前连通块高度
flag = false; // 不满足山丘条件
if (h[nx][ny] == val && !vis[nx][ny]) // 如果相邻点高度相同且未访问
{
q.push({nx, ny}); // 加入队列
vis[nx][ny] = true; // 标记已访问
}
}
}
return flag; // 返回当前连通块是否满足山丘条件
}
int main()
{
cin >> n >> m; // 输入行数和列数
for (int i = 1; i <= n; i++) // 读入高度图
for (int j = 1; j <= m; j++)
cin >> h[i][j];
int ans = 0; // 山丘数量
for (int i = 1; i <= n; i++) // 遍历整个地图
for (int j = 1; j <= m; j++)
{
if (!vis[i][j]) // 如果当前点未被访问
{
if (bfs(i, j)) // 执行BFS,判断是否是山丘
ans++; // 如果是山丘,计数加1
}
}
cout << ans << endl; // 输出山丘数量
return 0;
}
【运行结果】
8 7
4 3 2 2 1 0 1
3 3 3 2 1 0 1
2 2 2 2 1 0 0
2 1 1 1 1 0 0
1 1 0 0 0 1 0
0 0 0 1 1 1 0
0 1 2 2 1 1 0
0 1 1 1 2 1 0
3
浙公网安备 33010602011771号