题解:洛谷 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
【核心思想】
-
问题分析:给定一个 \(M \times N\) 的网格池塘,其中包含水、莲花、岩石、起点和终点。贝西每次跳跃方式为"日"字形(先横移 \(M1\) 再纵移 \(M2\),或先纵移 \(M1\) 再横移 \(M2\)),共 \(8\) 个方向。要求计算从起点到终点的最少跳跃步数。这是一个最短路径问题,关键在于利用 BFS(广度优先搜索) 逐层扩展,保证第一次到达终点时的步数即为最小值。
-
算法选择:
- BFS(Breadth-First Search):按步数逐层扩展搜索,天然适合求无权图的最短路径
- 二维网格建模:将池塘映射为二维网格,每个可通行格子作为节点,合法跳跃作为边
-
关键步骤:
- 初始化:读取 \(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\)
- 输出最小步数
-
时间/空间复杂度:
- 时间复杂度:\(O(M \times N)\),每个格子最多被访问一次,每个格子扩展 \(8\) 个方向
- 空间复杂度:\(O(M \times N)\),\(\text{Map}\) 数组、\(\text{Visit}\) 数组和 BFS 队列的空间
-
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
浙公网安备 33010602011771号