题解:洛谷 B4557 扫雷

【题目来源】

洛谷:B4557 [GESP202606 四级] 扫雷 - 洛谷

【题目描述】

小杨同学正在游玩经典游戏「扫雷」,他想自己生成一个「扫雷」的地图。

小杨同学希望生成的地图大小为 \(n\)\(m\) 列,一共 \(n \times m\) 个区块。区块行号为 \(1, 2, \cdots, n\),列号为 \(1, 2, \cdots, m\)。其中一些区块为雷区,其它区块不为雷区。

小杨同学指定了 \(q\) 个区块为雷区,而其它区块均不为雷区。小杨同学希望你帮忙计算非雷区的区块,每个区块与多少个雷区相邻?

我们定义区块相邻,当且仅当两个区块至少有一个公共顶点(也就是说对于不在地图边缘的区块,周围 \(8\) 个区块均与其相邻)。

【输入】

输入包含 \(q + 1\) 行。

第一行,三个正整数 \(n\), \(m\)\(q\),分别表示地图行数和列数,以及雷区数量。

接下来的 \(q\) 行,每行有 \(2\) 个整数,分别表示第 \(i\) 个雷区的行号和列号。

保证输入的雷区不重复。

【输出】

输出 \(n\) 行,每行 \(m\) 个字符(使用空格分割),对于第 \(i\) 行第 \(j\) 列,输出地图对应区块的信息:

  1. 如果为雷区,输出 *
  2. 如果不是雷区,输出其相邻雷区数量(输出 \(0\)\(8\) 中的一个数字)。

【输入样例】

3 4 4
1 1
1 3
2 4
3 2

【输出样例】

* 2 * 2
2 3 3 *
1 * 2 1

【核心思想】

  1. 问题分析:给定 \(n \times m\) 的地图和 \(q\) 个雷区坐标,需要为每个非雷区区块计算其周围 \(8\) 个方向相邻区块中雷区的数量。雷区输出 *,非雷区输出 \(0 \sim 8\) 的数字。本质是二维网格上的邻域计数问题。

  2. 算法选择

    • 直接模拟(八方向枚举):对每个非雷区位置,枚举周围 \(8\) 个方向的相邻位置,统计其中雷区数量
    • 方向向量法:用两个偏移数组 \(dx[8]\)\(dy[8]\) 统一表示 \(8\) 个相邻方向的坐标变化
  3. 关键步骤

    • 初始化:创建标记数组 \(a[1..n][1..m]\),初始全为 \(0\)
    • 标记雷区:读入 \(q\) 个雷区坐标 \((x, y)\),设置 \(a[x][y] = 1\)
    • 邻域计数:对每个非雷区位置 \((i, j)\)
      • 初始化计数器 \(\text{cnt} = 0\)
      • 遍历 \(8\) 个方向 \(k \in [0, 7]\)
        • 计算相邻坐标 \((xx, yy) = (i + dx[k], j + dy[k])\)
        • \(\text{cnt} \leftarrow \text{cnt} + a[xx][yy]\)
      • 记录 \(b[i][j] = \text{cnt}\)
    • 输出结果:遍历地图,雷区输出 *,非雷区输出 \(b[i][j]\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n \times m \times 8 + q) = O(n \times m)\),每个位置最多检查 \(8\) 个方向
    • 空间复杂度:\(O(n \times m)\),存储雷区标记数组和计数数组
  5. 八方向枚举的核心思想

    • 方向向量统一化:用 \(dx = \{-1, -1, -1, 0, 1, 1, 1, 0\}\)\(dy = \{-1, 0, 1, 1, 1, 0, -1, -1\}\)\(8\) 个相邻方向的坐标变化编码为数组,避免写 \(8\) 条重复的边界判断语句
    • 边界安全隐含:题目未明确说明是否需要边界检查,但样例和常规实现中数组开大到 \(N = 505\) 且坐标从 \(1\) 开始,若雷区坐标在 \([1, n] \times [1, m]\) 范围内,则相邻位置可能越界(如第 \(1\) 行上方)。实际应增加边界判断 if (xx >= 1 && xx <= n && yy >= 1 && yy <= m),或采用更大的数组并初始化为 \(0\) 来避免越界问题
    • 空间换时间:用布尔数组 \(a\) 标记雷区位置,使得查询"某位置是否为雷"变为 \(O(1)\),统计时直接累加而非逐一判断
    • 分离计算与输出:先完整计算所有位置的雷数存入 \(b\) 数组,再统一格式化输出,避免在计算过程中混合输出逻辑
    • 适用于二维网格上的邻域统计、图像处理中的卷积基础、游戏地图生成等场景

【算法标签】

普及- #模拟

【代码详解】

#include <bits/stdc++.h>
using namespace std;
const int N = 505;                 // 常量:地图最大尺寸

int n, m, q;                       // n: 地图行数; m: 地图列数; q: 雷区数量
int dx[8] = {-1, -1, -1, 0, 1, 1, 1, 0};   // 8个方向的行偏移量(左上、上、右上、右、右下、下、左下、左)
int dy[8] = {-1, 0, 1, 1, 1, 0, -1, -1};   // 8个方向的列偏移量
int a[N][N], b[N][N];              // a[i][j]: 标记(i,j)是否为雷区(1是/0否); b[i][j]: (i,j)周围雷区数量

int main()
{
    cin >> n >> m >> q;            // 读入地图尺寸和雷区数量

    while (q--)                    // 循环读入 q 个雷区坐标
    {
        int i, j;                  // i: 雷区行号; j: 雷区列号
        cin >> i >> j;
        a[i][j] = 1;               // 标记该位置为雷区
    }

    for (int i = 1; i <= n; i++)  // 遍历地图每个位置,计算非雷区周围雷数
        for (int j = 1; j <= m; j++)
        {
            if (a[i][j] == 1)      // 跳过雷区
                continue;

            int cnt = 0;           // cnt: 当前位置周围雷区计数器

            for (int k = 0; k < 8; k++)  // 遍历8个相邻方向
            {
                int xx = i + dx[k], yy = j + dy[k];  // 相邻位置的坐标
                cnt += a[xx][yy];  // 累加相邻位置是否为雷区
            }

            b[i][j] = cnt;         // 记录当前位置周围雷区数量
        }

    for (int i = 1; i <= n; i++)  // 输出地图
    {
        for (int j = 1; j <= m; j++)
        {
            if (a[i][j] == 1)      // 如果是雷区
                cout << '*' << " ";  // 输出 '*'
            else                   // 如果不是雷区
                cout << b[i][j] << " ";  // 输出周围雷区数量
        }

        cout << endl;              // 每行输出后换行
    }

    return 0;
}

【运行结果】

3 4 4
1 1
1 3
2 4
3 2
* 2 * 2
2 3 3 *
1 * 2 1
posted @ 2026-07-01 15:17  团爸讲算法  阅读(78)  评论(0)    收藏  举报