题解:洛谷 P1825 Corn Maze
【题目来源】
洛谷:P1825 [USACO11OPEN] Corn Maze S - 洛谷
【题目描述】
去年秋天,农夫约翰带着奶牛们参观了一个玉米迷宫。但这不是一个普通的玉米迷宫:它有几个重力驱动的传送滑梯,可以让奶牛瞬间从迷宫中的一个点传送到另一个点。滑梯是双向的:奶牛可以瞬间从滑梯的起点滑到终点,或者从终点滑到起点。如果奶牛踩到滑梯的任一端,她必须使用滑梯。
传送操作不能连续使用,即,传送一次之后,必须通过非传送方式移动至少一步才能再次进行传送。
玉米迷宫的外部完全由玉米包围,只有一个出口。
迷宫可以用一个 \(N \times M\)(\(2 \leq N \leq 300\);\(2 \leq M \leq 300\))的网格表示。每个网格元素包含以下项目之一:
- 玉米(玉米网格元素不可通行)
- 草地(容易通过!)
- 滑梯端点(会将奶牛传送到另一个端点)
- 出口
奶牛只能从一个空间移动到相邻的下一个空间,前提是它们相邻且都不包含玉米。每个草地空间有四个潜在的邻居可以让奶牛到达。从一个草地空间移动到相邻空间需要 \(1\) 个时间单位;从一个滑梯端点移动到另一个端点需要 \(0\) 个时间单位。
填满玉米的空间用井号(#)表示。草地空间用英文句号(.)表示。滑梯端点对用相同的大写字母(A 到 Z)表示,并且没有两个不同的滑梯端点用相同的字母表示。出口用等号(=)表示。
贝茜迷路了。她知道自己在网格中的位置,并用「at」符号(@)标记了她当前的草地空间。她需要的最短时间是多少才能移动到出口空间?
【输入】
第一行:两个用空格隔开的整数 \(N\) 和 \(M\)。
第 \(2 \sim N+1\) 行:第 \(i+1\) 行描述了迷宫中的第 \(i\) 行的情况(共有 \(M\) 个字符,每个字符之间没有空格)。
【输出】
一个整数,表示起点到出口所需的最短时间。
【输入样例】
5 6
###=#
#.W.#
#.###
#.@W#
######
【输出样例】
3
【核心思想】
-
问题分析:给定 \(N \times M\) 的迷宫,包含草地
.、玉米#(障碍)、传送门A-Z(两端互相传送,耗时 \(0\))、起点@和出口=。传送后必须至少走一格才能再次传送。求起点到出口的最短时间。本质上是BFS + 传送门处理问题:普通移动边权为 \(1\),传送边权为 \(0\),但传送后有冷却限制。 -
算法选择:
- BFS:边权为 \(0\) 或 \(1\),但传送冷却使问题复杂化,标准 BFS 配合访问标记即可
- 传送门预处理:用结构体数组记录每个字母传送门的两个端点坐标
-
关键步骤:
- 读取数据:读入 \(N, M\) 和迷宫,预处理传送门端点,找到起点并入队
- BFS 循环:
- 取出队首节点 \((x, y, step)\)
- 遍历四个方向 \((nx, ny)\):
- 边界和障碍物检查
- 若为空地
.:\(step+1\) 入队,标记为# - 若为出口
=:输出 \(step+1\),结束 - 若为传送门:传送到另一端点,\(step+1\) 入队(不标记访问,因为传送后可能从另一端继续走)
- 输出最短时间
-
时间/空间复杂度:
- 时间复杂度:\(O(N \cdot M)\),每个格子最多访问一次
- 空间复杂度:\(O(N \cdot M)\),队列和地图数组
-
BFS + 传送门的核心思想:
- 传送的零代价性:传送门两端移动耗时为 \(0\),但 BFS 中统一按 \(step+1\) 处理(因为走到传送门入口已花费 \(1\) 步)
- 传送冷却的隐含处理:代码中传送后直接入队,从另一端继续 BFS,自然满足"传送后必须走一格"的约束(因为从传送门出口走出需要一步)
- 访问标记策略:普通草地标记为
#防止重复访问,传送门不标记(因为可能从两端多次使用,但 BFS 的层次性保证不会无限循环) - 双向传送:每个字母对应两个端点,踩到任一端都传送到另一端
- 适用于迷宫最短路、传送门机制、BFS 扩展类问题
【解题思路】

【算法标签】
普及 #BFS-二维
【代码详解】
#include <bits/stdc++.h>
using namespace std;
int n, m; // 地图的行数和列数
// 四个方向的偏移量:上、下、左、右
int dx[4] = {-1, 1, 0, 0}, dy[4] = {0, 0, -1, 1};
char a[305][305]; // 存储地图
char tmp; // 临时字符变量
// 定义位置结构体,包含坐标和步数
struct node
{
int x, y, step;
};
// 定义传送门结构体,记录两个端点的坐标
struct mark
{
int x1, y1, x2, y2;
} cs[100]; // 最多26个字母的传送门
int main()
{
// 输入地图尺寸
cin >> n >> m;
// 初始化BFS队列
queue<node> q;
// 读取地图数据
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
cin >> a[i][j];
tmp = a[i][j];
// 如果是传送门(A-Z)
if (tmp >= 'A' && tmp <= 'Z')
{
// 记录传送门的两个端点
if (cs[tmp].x1 == 0)
{
cs[tmp].x1 = i;
cs[tmp].y1 = j;
}
else
{
cs[tmp].x2 = i;
cs[tmp].y2 = j;
}
}
// 如果是起点
if (tmp == '@')
{
node tp = {i, j, 0}; // 初始步数为0
q.push(tp);
a[i][j] = '#'; // 标记为已访问
}
}
}
// BFS主循环
while (!q.empty())
{
node tp = q.front();
q.pop();
// 尝试四个方向移动
for (int i = 0; i < 4; i++)
{
int xx = tp.x + dx[i];
int yy = tp.y + dy[i];
// 检查边界
if (xx < 1 || xx > n || yy < 1 || yy > m) continue;
// 如果是障碍物
if (a[xx][yy] == '#') continue;
// 如果是可通行的空地
if (a[xx][yy] == '.')
{
node t = {xx, yy, tp.step + 1};
q.push(t);
a[xx][yy] = '#'; // 标记为已访问
continue;
}
// 如果是终点
if (a[xx][yy] == '=')
{
cout << tp.step + 1; // 输出步数
return 0;
}
// 如果是传送门
if (cs[a[xx][yy]].x1 == xx && cs[a[xx][yy]].y1 == yy)
{
// 传送到另一端
node t = {cs[a[xx][yy]].x2, cs[a[xx][yy]].y2, tp.step + 1};
q.push(t);
}
else
{
// 传送到另一端
node t = {cs[a[xx][yy]].x1, cs[a[xx][yy]].y1, tp.step + 1};
q.push(t);
}
}
}
return 0;
}
【运行结果】
5 6
###=#
#.W.#
#.###
#.@W#
######
3
浙公网安备 33010602011771号