题解:洛谷 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
【核心思想】
-
问题分析:给定 \(H \times W\) 的迷宫,包含空格子
.、障碍#和传送格(小写字母a-z)。从 \((1,1)\) 出发,每次可四方向行走(不能越界或走障碍),或从传送格瞬间传送到任意同字母传送格(消耗 \(1\) 次操作)。求到达 \((H,W)\) 的最少操作次数。这是一个BFS + 传送门预处理问题,关键在于将传送操作建模为边权为 \(1\) 的特殊边,并用标记避免重复传送导致死循环。 -
算法选择:
- BFS(广度优先搜索):所有操作(行走和传送)边权均为 \(1\),天然适合用 BFS 求最短路
- 传送门预处理:用
vector<PII> ve[26]预先存储每种字母的所有坐标,实现 \(O(1)\) 获取同字母传送点集合 - 字母使用标记:
st[26]记录每种字母的传送功能是否已使用过,防止同一字母多次传送导致重复入队
-
关键步骤:
- 预处理:读入网格,将所有小写字母坐标按字母分类存入 \(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\)(该字母传送功能全局仅使用一次)
- 遍历 \(ve[c]\) 中所有坐标 \((x_2, y_2)\):
- 行走分支:遍历四方向 \((nx, ny)\):
- 边界检查、障碍检查、访问检查均通过后:\(dist[nx][ny] = dist[x][y] + 1\),入队
- 传送分支:若 \(a[x][y]\) 为小写字母 \(c\) 且 \(st[c] = false\):
- 输出:若 \(dist[H][W] = -1\) 输出 \(-1\),否则输出 \(dist[H][W]\)
-
时间/空间复杂度:
- 时间复杂度:\(O(H \cdot W + \sum_{c=0}^{25} |ve[c]|)\),每个格子最多入队一次,每种字母的传送点最多被遍历一次
- 空间复杂度:\(O(H \cdot W)\),距离数组、访问标记、队列及传送点存储
-
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
浙公网安备 33010602011771号