题解:洛谷 P2385 Lilypad Pond

【题目来源】

洛谷:P2385 [USACO07FEB] Bronze Lilypad Pond B - 洛谷

【题目描述】

为了让奶牛们娱乐和锻炼,农夫约翰建造了一个美丽的池塘。这个长方形的池子被分成了 \(M\)\(N\) 列个方格(\(1 ≤ M, N ≤ 30\))。一些格子是坚固得令人惊讶的莲花,还有一些格子是岩石,其余的只是美丽、纯净、湛蓝的水。

贝西正在练习芭蕾舞,她站在一朵莲花上,想跳到另一朵莲花上去,她只能从一朵莲花跳到另一朵莲花上,既不能跳到水里,也不能跳到岩石上。

贝西的舞步很像象棋中的马步:每次总是先横向移动 \(M1\) (\(1 ≤ M1 ≤ 30\))格,再纵向移动 \(M2\) (\(1 ≤ M2 ≤ 30\), \(M1≠M2\))格,或先纵向移动 \(M1\) 格,再横向移动 \(M2\) 格。最多时,贝西会有八个移动方向可供选择。

给定池塘的布局和贝西的跳跃长度,请计算贝西从起点出发,到达目的地的最小步数,我们保证输入数据中的目的地一定是可达的。

【输入】

第一行:四个用空格分开的整数:\(M\)\(N\)\(M1\)\(M2\)

第二行到 \(M + 1\) 行:第 \(i + 1\) 行有 \(N\) 个用空格分开的整数,描述了池塘第

\(i\) 行的状态:\(0\) 为水,\(1\) 为莲花,\(2\) 为岩石,\(3\) 为贝西所在的起点,\(4\) 为贝西想去的终点。

【输出】

第一行:从起点到终点的最少步数。

【输入样例】

4 5 1 2
1 0 1 0 1
3 0 2 0 4
0 1 2 0 0
0 0 0 1 0

【输出样例】

2

【核心思想】

  1. 问题分析:给定一个 \(M \times N\) 的网格池塘,其中包含水、莲花、岩石、起点和终点。贝西每次跳跃方式为"日"字形(先横移 \(M1\) 再纵移 \(M2\),或先纵移 \(M1\) 再横移 \(M2\)),共 \(8\) 个方向。要求计算从起点到终点的最少跳跃步数。这是一个最短路径问题,关键在于利用 BFS(广度优先搜索) 逐层扩展,保证第一次到达终点时的步数即为最小值。

  2. 算法选择

    • BFS(Breadth-First Search):按步数逐层扩展搜索,天然适合求无权图的最短路径
    • 二维网格建模:将池塘映射为二维网格,每个可通行格子作为节点,合法跳跃作为边
  3. 关键步骤

    • 初始化:读取 \(M\)(行数)、\(N\)(列数)、\(M1\)\(M2\)(跳跃步长),以及网格状态
    • 预处理
      • 遍历网格,标记可通行位置(莲花 \(1\)、起点 \(3\)、终点 \(4\))为 \(\text{true}\),岩石 \(2\) 和水 \(0\)\(\text{false}\)
      • 记录起点坐标 \((sx, sy)\) 和终点坐标 \((ex, ey)\)
      • 初始化 \(8\) 个跳跃方向的位移数组 \(\text{dx}[8]\)\(\text{dy}[8]\)
        • \((\pm M1, \pm M2)\)\((\pm M2, \pm M1)\) 的所有组合
    • BFS 搜索
      • 起点 \((sx, sy)\) 入队,步数 \(\text{step} = 0\),标记已访问
      • 当队列非空时:
        • 取出队首节点 \((x, y, \text{step})\)
        • \((x, y) = (ex, ey)\),返回当前 \(\text{step}\)
        • 遍历 \(8\) 个方向,计算新坐标 \((nx, ny) = (x + \text{dx}[k], y + \text{dy}[k])\)
        • 检查边界(\(1 \le nx \le M\)\(1 \le ny \le N\))、可通行性(\(\text{Map}[nx][ny] = \text{true}\))和未访问(\(\text{Visit}[nx][ny] = \text{false}\)
        • 满足条件则标记访问并入队,步数为 \(\text{step} + 1\)
    • 输出最小步数
  4. 时间/空间复杂度

    • 时间复杂度:\(O(M \times N)\),每个格子最多被访问一次,每个格子扩展 \(8\) 个方向
    • 空间复杂度:\(O(M \times N)\)\(\text{Map}\) 数组、\(\text{Visit}\) 数组和 BFS 队列的空间
  5. BFS 的核心思想

    • 逐层扩展:BFS 按距离起点的步数从小到大依次访问节点,第一次到达终点时即为最短路径
    • 访问标记\(\text{Visit}\) 数组防止重复访问,避免死循环和冗余计算
    • 方向数组:用 \(\text{dx}[8]\)\(\text{dy}[8]\) 统一处理 \(8\) 个跳跃方向,代码简洁且不易出错
    • 状态表示:每个节点存储坐标 \((x, y)\) 和当前步数 \(\text{step}\),便于直接判断是否到达终点
    • 适用于无权图的最短路径、迷宫寻路、网格遍历等类问题

【算法标签】

普及 #BFS-二维

【代码详解】

#include <bits/stdc++.h>
using namespace std;

const int N = 35;  // 定义网格最大尺寸

// 全局变量定义
int m, n;          // 网格的行数和列数
int m1, n1;        // 移动步长的水平和垂直分量
int sx, sy;        // 起点坐标
int ex, ey;        // 终点坐标
int ch;            // 临时存储网格输入值

// 定义节点结构体(坐标和步数)
struct Node
{
    int x, y, step;
};

queue<Node> Q;     // BFS队列
int dx[8];         // x方向移动步长数组
int dy[8];         // y方向移动步长数组
bool Map[N][N];    // 存储网格可通行性
bool Visit[N][N];  // 标记数组,记录位置是否已访问

// 广度优先搜索函数
int bfs(int x, int y)
{
    // 初始化访问标记
    memset(Visit, false, sizeof(Visit));

    // 起点入队并标记
    Q.push({x, y, 0});
    Visit[x][y] = true;

    while (!Q.empty())
    {
        Node nown = Q.front();
        Q.pop();

        // 到达终点返回步数
        if (nown.x == ex && nown.y == ey)
            return nown.step;

        // 尝试8个方向的移动
        for (int k = 0; k < 8; k++)
        {
            int nx = nown.x + dx[k];
            int ny = nown.y + dy[k];

            // 检查边界和可通行性
            if (nx < 1 || nx > m || ny < 1 || ny > n)
                continue;
            if (!Map[nx][ny])
                continue;
            if (Visit[nx][ny])
                continue;

            // 标记并加入队列
            Visit[nx][ny] = true;
            Q.push(Node{nx, ny, nown.step + 1});
        }
    }
    return -1;  // 无法到达终点
}

int main()
{
    // 输入网格尺寸和移动步长
    cin >> m >> n >> m1 >> n1;

    // 输入网格数据并标记起点终点
    for (int i = 1; i <= m; i++)
    {
        for (int j = 1; j <= n; j++)
        {
            cin >> ch;
            // 标记可通行区域(1、3、4为可通行)
            if (ch == 1 || ch == 3 || ch == 4)
                Map[i][j] = true;
            // 记录起点(3)
            if (ch == 3)
            {
                sx = i;
                sy = j;
            }
            // 记录终点(4)
            if (ch == 4)
            {
                ex = i;
                ey = j;
            }
        }
    }

    // 初始化8个移动方向
    dx[0] = m1; dy[0] = n1;
    dx[1] = m1; dy[1] = -n1;
    dx[2] = -m1; dy[2] = n1;
    dx[3] = -m1; dy[3] = -n1;
    dx[4] = n1; dy[4] = m1;
    dx[5] = n1; dy[5] = -m1;
    dx[6] = -n1; dy[6] = m1;
    dx[7] = -n1; dy[7] = -m1;

    // 执行BFS并输出结果
    int ans = bfs(sx, sy);
    cout << ans << endl;

    return 0;
}

【运行结果】

4 5 1 2
1 0 1 0 1
3 0 2 0 4
0 1 2 0 0
0 0 0 1 0
2
posted @ 2026-08-17 09:46  团爸讲算法  阅读(12)  评论(0)    收藏  举报