题解:洛谷 AT_abc436_d Teleport Maze

【题目来源】

洛谷:AT_abc436_d [ABC436D] Teleport Maze - 洛谷

【题目描述】

有一个由 \(H\)\(W\) 列组成的迷宫。我们用 \((i,j)\) 表示从上往下第 \(i\) 行、从左往右第 \(j\) 列的格子。每个格子的类型由一个字符 \(S_{i,j}\) 给定,具体含义如下:

  • .:空格子
  • #:障碍格子
  • 小写英文字母(a - z):传送格子

你可以不限次数、顺序进行以下两种操作:

  • 行走:从当前位置移动到相邻的上下左右四个方向的其中一个格子,但不能走到障碍格或出界。
  • 传送:当你在一个传送格上时,可以立刻移动到任何一个字符相同的传送格上。

请你判断是否可以从 \((1,1)\) 出发,移动到 \((H,W)\)。如果可以,请输出所需的最少操作次数;否则输出 \(-1\)

【输入】

输入从标准输入读入,格式如下:

\(H\) \(W\)
\(S_{1,1}S_{1,2}\dots S_{1,W}\)
\(\vdots\)
\(S_{H,1}S_{H,2}\dots S_{H,W}\)

【输出】

如果可以从 \((1,1)\) 到达 \((H,W)\),输出所需的最少操作次数;否则输出 \(-1\)

【输入样例】

3 4
..a.
####
ba#b

【输出样例】

5

【核心思想】

  1. 问题分析:给定 \(H \times W\) 的迷宫,包含空格子 .、障碍 # 和传送格(小写字母 a-z)。从 \((1,1)\) 出发,每次可四方向行走(不能越界或走障碍),或从传送格瞬间传送到任意同字母传送格(消耗 \(1\) 次操作)。求到达 \((H,W)\) 的最少操作次数。这是一个BFS + 传送门预处理问题,关键在于将传送操作建模为边权为 \(1\) 的特殊边,并用标记避免重复传送导致死循环。

  2. 算法选择

    • BFS(广度优先搜索):所有操作(行走和传送)边权均为 \(1\),天然适合用 BFS 求最短路
    • 传送门预处理:用 vector<PII> ve[26] 预先存储每种字母的所有坐标,实现 \(O(1)\) 获取同字母传送点集合
    • 字母使用标记st[26] 记录每种字母的传送功能是否已使用过,防止同一字母多次传送导致重复入队
  3. 关键步骤

    • 预处理:读入网格,将所有小写字母坐标按字母分类存入 \(ve[c]\)\(c = 0..25\)),初始化 \(dist\)\(-1\)
    • BFS 初始化:起点 \((1,1)\) 入队,\(dist[1][1] = 0\)
    • 队列扩展(取出队首 \((x, y)\)):
      • 传送分支:若 \(a[x][y]\) 为小写字母 \(c\)\(st[c] = false\)
        • 遍历 \(ve[c]\) 中所有坐标 \((x_2, y_2)\)
          • \(dist[x_2][y_2] = -1\)(未访问):\(dist[x_2][y_2] = dist[x][y] + 1\),入队
        • 标记 \(st[c] = true\)(该字母传送功能全局仅使用一次)
      • 行走分支:遍历四方向 \((nx, ny)\)
        • 边界检查、障碍检查、访问检查均通过后:\(dist[nx][ny] = dist[x][y] + 1\),入队
    • 输出:若 \(dist[H][W] = -1\) 输出 \(-1\),否则输出 \(dist[H][W]\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(H \cdot W + \sum_{c=0}^{25} |ve[c]|)\),每个格子最多入队一次,每种字母的传送点最多被遍历一次
    • 空间复杂度:\(O(H \cdot W)\),距离数组、访问标记、队列及传送点存储
  5. BFS + 传送门的核心思想

    • 边权一致性:行走和传送均消耗 \(1\) 次操作,BFS 的层序遍历天然保证最短距离
    • 传送门全局标记\(st[c]\) 确保每种字母的传送边仅被松弛一次,避免同一字母的多个传送点之间反复横跳导致 \(O(|ve[c]|^2)\) 的冗余扩展
    • 分层图思想:将传送操作视为连接同字母所有点的完全图,通过预处理 + 懒加载(首次遇到该字母时才展开)降低实际复杂度
    • 适用于"网格最短路 + 特殊跳跃边"类问题,核心是控制特殊边的展开时机以避免重复

【算法标签】

普及 #BFS-二维

【代码详解】

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

const int N = 1005;  // 最大网格大小
typedef pair<int, int> PII;  // 坐标对

int h, w;            // 网格高度和宽度
char a[N][N];        // 网格内容
int dist[N][N];      // 从起点到每个点的最短距离
bool vis[N][N];      // 访问标记(未使用)
int dx[4] = {-1, 1, 0, 0};  // 上下左右方向
int dy[4] = {0, 0, -1, 1};
vector<PII> ve[30];  // 存储每种小写字母的位置
bool st[30];         // 标记每种字母是否已使用过传送功能

/**
 * BFS求从(1,1)到(h,w)的最短路径
 * 支持普通移动和特殊传送
 */
void bfs()
{
    queue<PII> q;
    q.push({1, 1});        // 起点
    dist[1][1] = 0;        // 起点距离为0

    while (!q.empty())
    {
        int x = q.front().first, y = q.front().second;
        q.pop();

        // 如果当前格是小写字母,且该字母的传送功能未使用过
        if (islower(a[x][y]) && st[a[x][y] - 'a'] == false)
        {
            // 遍历该字母对应的所有传送点
            for (auto [x2, y2] : ve[a[x][y] - 'a'])
            {
                // 如果目标点未访问过
                if (dist[x2][y2] == -1)
                {
                    // 距离为当前位置距离+1
                    dist[x2][y2] = dist[x][y] + 1;
                    // 加入队列
                    q.push({x2, y2});
                }
            }
            // 标记该字母的传送功能已使用
            st[a[x][y] - 'a'] = true;
        }

        // 四个方向普通移动
        for (int i = 0; i < 4; i++)
        {
            int nx = x + dx[i], ny = y + dy[i];
            // 边界检查
            if (nx < 1 || nx > h || ny < 1 || ny > w)
                continue;
            // 障碍物检查
            if (a[nx][ny] == '#')
                continue;
            // 已访问检查
            if (dist[nx][ny] != -1)
                continue;
            // 入队并更新距离
            q.push({nx, ny});
            dist[nx][ny] = dist[x][y] + 1;
        }
    }
}

int main()
{
    // 输入网格大小
    cin >> h >> w;

    // 初始化距离为-1(表示未访问)
    memset(dist, -1, sizeof(dist));

    // 读入网格并预处理字母位置
    for (int i = 1; i <= h; i++)
    {
        for (int j = 1; j <= w; j++)
        {
            cin >> a[i][j];
            // 如果是小写字母,记录其位置
            if (islower(a[i][j]))
            {
                ve[a[i][j] - 'a'].push_back({i, j});
            }
        }
    }

    // BFS求最短路径
    bfs();

    // 输出结果
    if (dist[h][w] == -1)
    {
        cout << -1 << endl;  // 不可达
    }
    else
    {
        cout << dist[h][w] << endl;  // 最短距离
    }

    return 0;
}

【运行结果】

3 4
..a.
####
ba#b
5
posted @ 2026-08-27 11:04  团爸讲算法  阅读(10)  评论(0)    收藏  举报