题解:洛谷 P1825 Corn Maze

【题目来源】

洛谷:P1825 [USACO11OPEN] Corn Maze S - 洛谷

【题目描述】

去年秋天,农夫约翰带着奶牛们参观了一个玉米迷宫。但这不是一个普通的玉米迷宫:它有几个重力驱动的传送滑梯,可以让奶牛瞬间从迷宫中的一个点传送到另一个点。滑梯是双向的:奶牛可以瞬间从滑梯的起点滑到终点,或者从终点滑到起点。如果奶牛踩到滑梯的任一端,她必须使用滑梯。

传送操作不能连续使用,即,传送一次之后,必须通过非传送方式移动至少一步才能再次进行传送。

玉米迷宫的外部完全由玉米包围,只有一个出口。

迷宫可以用一个 \(N \times M\)\(2 \leq N \leq 300\)\(2 \leq M \leq 300\))的网格表示。每个网格元素包含以下项目之一:

  • 玉米(玉米网格元素不可通行)
  • 草地(容易通过!)
  • 滑梯端点(会将奶牛传送到另一个端点)
  • 出口

奶牛只能从一个空间移动到相邻的下一个空间,前提是它们相邻且都不包含玉米。每个草地空间有四个潜在的邻居可以让奶牛到达。从一个草地空间移动到相邻空间需要 \(1\) 个时间单位;从一个滑梯端点移动到另一个端点需要 \(0\) 个时间单位。

填满玉米的空间用井号(#)表示。草地空间用英文句号(.)表示。滑梯端点对用相同的大写字母(AZ)表示,并且没有两个不同的滑梯端点用相同的字母表示。出口用等号(=)表示。

贝茜迷路了。她知道自己在网格中的位置,并用「at」符号(@)标记了她当前的草地空间。她需要的最短时间是多少才能移动到出口空间?

【输入】

第一行:两个用空格隔开的整数 \(N\)\(M\)

\(2 \sim N+1\) 行:第 \(i+1\) 行描述了迷宫中的第 \(i\) 行的情况(共有 \(M\) 个字符,每个字符之间没有空格)。

【输出】

一个整数,表示起点到出口所需的最短时间。

【输入样例】

5 6
###=#
#.W.#
#.###
#.@W#
######

【输出样例】

3

【核心思想】

  1. 问题分析:给定 \(N \times M\) 的迷宫,包含草地 .、玉米 #(障碍)、传送门 A-Z(两端互相传送,耗时 \(0\))、起点 @ 和出口 =。传送后必须至少走一格才能再次传送。求起点到出口的最短时间。本质上是BFS + 传送门处理问题:普通移动边权为 \(1\),传送边权为 \(0\),但传送后有冷却限制。

  2. 算法选择

    • BFS:边权为 \(0\)\(1\),但传送冷却使问题复杂化,标准 BFS 配合访问标记即可
    • 传送门预处理:用结构体数组记录每个字母传送门的两个端点坐标
  3. 关键步骤

    • 读取数据:读入 \(N, M\) 和迷宫,预处理传送门端点,找到起点并入队
    • BFS 循环
      • 取出队首节点 \((x, y, step)\)
      • 遍历四个方向 \((nx, ny)\)
        • 边界和障碍物检查
        • 若为空地 .\(step+1\) 入队,标记为 #
        • 若为出口 =:输出 \(step+1\),结束
        • 若为传送门:传送到另一端点,\(step+1\) 入队(不标记访问,因为传送后可能从另一端继续走)
    • 输出最短时间
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N \cdot M)\),每个格子最多访问一次
    • 空间复杂度:\(O(N \cdot M)\),队列和地图数组
  5. 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
posted @ 2026-08-17 09:46  团爸讲算法  阅读(26)  评论(0)    收藏  举报