题解:洛谷 B4501 山之谷

【题目来源】

洛谷:B4501 [GESP202603 四级] 山之谷 - 洛谷

【题目描述】

现有一片山地,可以视为一个 \(N\)\(M\) 列的网格图,第 \(i\)\(j\) 列的海拔为 \(h_{i,j}\)

如果一个单元格的海拔不高于其所有相邻单元格(相邻包括上、下、左、右、左上、右上、左下、右下,最多 \(8\) 个方向)的海拔,则称该单元格为山谷。

请你数一数该片山地中有多少山谷。

【输入】

第一行包含 \(2\) 个整数 \(N, M\),表示山地的大小。

之后 \(N\) 行,每行包含 \(M\) 个整数 \(h_{i,1}, h_{i,2}, \cdots, h_{i,M}\),表示海拔。

【输出】

输出 \(1\) 行,包含 \(1\) 个整数 \(C\),表示山谷的数量。

【输入样例】

3 5
7 6 6 7 9
6 5 6 7 6
6 5 7 8 9

【输出样例】

3

【核心思想】

  1. 问题分析:给定 \(N \times M\) 的海拔网格,需要统计"山谷"数量。山谷定义为海拔不高于所有 \(8\) 个方向相邻单元格(上、下、左、右及四个对角线方向)的单元格。这是一个网格遍历 + 方向枚举问题,核心在于对每个单元格检查其 \(8\) 个邻居的海拔关系。

  2. 算法选择

    • 方向向量枚举:预定义 \(8\) 个方向的偏移量 \((dx_k, dy_k)\),统一处理所有相邻位置
    • 逐格判定:遍历每个单元格,检查其所有合法邻居是否均 \(\geq\) 当前单元格海拔
  3. 关键步骤

    • 读入数据:读取 \(N, M\) 和海拔矩阵 \(h[1..N][1..M]\)
    • 方向数组定义\(dx = [-1, -1, -1, 0, 1, 1, 1, 0]\)\(dy = [-1, 0, 1, 1, 1, 0, -1, -1]\),对应 \(8\) 个相邻方向
    • 逐格判定山谷(遍历 \(i\)\(1\)\(N\)\(j\)\(1\)\(M\)):
      • \(flag \leftarrow true\)
      • 遍历 \(8\) 个方向,计算邻居坐标 \((nx, ny) = (i + dx_k, j + dy_k)\)
      • 若邻居在边界内且 \(h_{nx,ny} < h_{i,j}\)\(flag \leftarrow false\)\(break\)
      • \(flag = true\)\(cnt \leftarrow cnt + 1\)
    • 输出结果\(cnt\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N \cdot M)\),每个单元格检查 \(8\) 个方向,共 \(8NM\) 次比较
    • 空间复杂度:\(O(N \cdot M)\),存储海拔矩阵
  5. 方向枚举与边界处理的核心思想

    • 统一方向处理:通过预定义 \(8\) 个方向向量,将不同方向的邻居检查统一为坐标加法运算,避免重复编写边界判断逻辑
    • 提前终止优化:一旦发现某个邻居海拔严格小于当前单元格,立即判定不是山谷并跳出循环,减少不必要的比较
    • 边界安全判定:通过 \(nx \in [1,N]\)\(ny \in [1,M]\) 的条件过滤越界邻居,确保不会访问数组非法位置
    • 不严格小于的判定:题目要求"不高于所有相邻单元格",即 \(h_{i,j} \leq h_{neighbor}\) 对所有邻居成立,等价于不存在邻居 \(h_{neighbor} < h_{i,j}\)
    • 适用于网格图上的局部极值统计、邻域关系判定类基础问题

【算法标签】

普及- #模拟

【代码详解】

#include <bits/stdc++.h>  // 包含所有标准库头文件
using namespace std;       // 使用标准命名空间

const int N = 105;  // 定义常量N,表示数组最大尺寸
int n, m, cnt;      // n:行数, m:列数, cnt:山谷计数器
int a[N][N];        // 定义二维数组a,存储地形高度

// 定义8个方向向量,用于访问当前位置周围的8个邻居
int dx[8] = {-1, -1, -1, 0, 1, 1, 1, 0};  // x方向偏移:左、左上、上、右上、右、右下、下、左下
int dy[8] = {-1, 0, 1, 1, 1, 0, -1, -1};  // y方向偏移:上、上、上、右、右、右、下、下

int main()  // 主函数入口
{
    cin >> n >> m;  // 输入矩阵的行数n和列数m

    // 读取矩阵数据
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            cin >> a[i][j];  // 读取第i行第j列的高度
        }
    }

    // 遍历矩阵中的每一个位置
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            bool flag = 1;  // 标志位,表示当前位置是否是山谷,初始化为true

            // 检查当前位置的8个邻居
            for (int k = 0; k < 8; k++)
            {
                // 计算邻居位置的坐标
                int nx = i + dx[k];  // 邻居的x坐标
                int ny = j + dy[k];  // 邻居的y坐标

                // 检查邻居是否在矩阵范围内
                if (nx < 1 || nx > n || ny < 1 || ny > m)
                {
                    continue;  // 如果邻居越界,跳过这个邻居
                }

                // 检查山谷条件:如果邻居高度小于当前位置高度,则不是山谷
                if (a[nx][ny] < a[i][j])
                {
                    flag = 0;  // 标记当前位置不是山谷
                    break;     // 提前退出循环,不再检查其他邻居
                }
            }

            // 如果flag仍为1,说明所有邻居都不小于当前位置,当前位置是山谷
            if (flag)
            {
                cnt++;  // 山谷计数器加1
            }
        }
    }

    cout << cnt << endl;  // 输出山谷的总数量
    return 0;  // 程序正常结束
}

【运行结果】

3 5
7 6 6 7 9
6 5 6 7 6
6 5 7 8 9
3
posted @ 2026-07-16 14:58  团爸讲算法  阅读(5)  评论(0)    收藏  举报