当前位置: 首页 > news >正文

迷宫传送[最短路径]

问题描述

有一个由 H 行、W 列的网格组成的迷宫。用 (i, j) 表示从上往下第 i 行、从左往右第 j 列的格子。

每个格子 (i, j) 的类型由一个字符 S(i, j) 给出,其含义如下:

  • '.' :空单元格

  • '#' :障碍单元格

  • 小写英文字母 (a–z) :传送单元格

在迷宫中,你可以按任意顺序执行以下两种动作,次数不限:

  1. 行走:从当前单元格移动到相邻的上、下、左、右单元格之一。但不能移动到障碍格或网格外。

  2. 传送:当你位于传送单元格时,可以移动到相同字母的任意传送单元格。

请判断能否从单元格 (1,1) 移动到单元格 (H,W) 。如果可能,请输出所需的最小总动作次数;否则输出 -1。

约束条件

  • 1 ≤ H, W ≤ 1000

  • H × W ≥ 2

  • H, W 为整数

  • S(i, j) 是 '.'、'#' 或小写英文字母

  • S(1,1) ≠ '#'

  • S(H,W) ≠ '#'

输入格式

H W S(1,1) S(1,2) … S(1,W) … S(H,1) S(H,2) … S(H,W)

输出格式

如果可以从 (1,1) 移动到 (H,W),则输出所需的最小总动作数;否则输出 -1。

问题分析

这是一个在网格中寻找从起点(1,1)到终点(H,W)最短路径的问题,但有一个特殊机制:传送。当站在某个字母格子上时,可以瞬间传送到所有相同字母的格子上。这相当于在图中添加了大量"快捷边"。

核心思路

1. 图模型转换

  • 每个格子是一个节点

  • 相邻格子(上下左右)之间有一条无向边(代价为1)

  • 相同字母的所有格子之间完全连接(互相可达,代价为1)

2. 朴素方法的陷阱

如果直接将所有相同字母的格子之间都建边,复杂度会爆炸:

  • 假设有k个字母'a'的格子,它们之间需要建立O(k²)条边

  • 当k很大时(比如样例3中全是'x',k=H×W=16),边数达到O((HW)²),不可接受

3. 关键优化:传送门的一次性使用

核心观察:每个字母的传送机制只需要在第一次遇到该字母时使用一次

为什么?

  1. 第一次遇到字母c的格子时,通过一次传送动作,可以到达所有字母c的格子

  2. 这些新到达的格子,到起点的距离 = 当前距离 + 1

  3. 之后如果再从其他路径到达字母c的格子,距离一定 ≥ 当前距离 + 1

  4. 通过BFS的性质,第一次访问时就是最短路径

  5. 所以之后再次访问同字母的格子不会得到更短路径

因此,我们可以:

  • 在第一次访问某个字母的任意格子时,将该字母的所有格子都加入队列

  • 然后立即清空这个字母的格子列表

  • 之后遇到相同字母的格子时,不再进行传送操作

算法实现详解

数据结构设计

vector<string> S(H); // 存储迷宫 vector<vector<pair<int, int>>> tele(26); // 26个字母对应的格子列表 vector<vector<int>> dist(H, vector<int>(W, INF)); // 最短距离 queue<pair<int, int>> q; // BFS队列

预处理:收集传送门

for (int i = 0; i < H; i++) { for (int j = 0; j < W; j++) { if (S[i][j] >= 'a' && S[i][j] <= 'z') { tele[S[i][j] - 'a'].emplace_back(i, j); } } }

这里为每个小写字母建立一个列表,存储所有该字母格子的坐标。

BFS主循环

while (!q.empty()) { auto [x, y] = q.front(); q.pop(); int d = dist[x][y]; // 到达终点,直接输出(BFS第一次到达就是最短) if (x == H-1 && y == W-1) { cout << d << "\n"; return 0; } // 1. 行走扩展:向四个方向移动 for (int k = 0; k < 4; k++) { int nx = x + dx[k]; int ny = y + dy[k]; // 检查边界、不是障碍、且未访问过 if (nx >= 0 && nx < H && ny >= 0 && ny < W && S[nx][ny] != '#' && dist[nx][ny] > d + 1) { dist[nx][ny] = d + 1; q.emplace(nx, ny); } } // 2. 传送扩展:如果当前是字母格子 if (S[x][y] >= 'a' && S[x][y] <= 'z') { int c = S[x][y] - 'a'; // 遍历该字母的所有格子 for (auto [tx, ty] : tele[c]) { // 如果这个格子还没被访问过 if (dist[tx][ty] > d + 1) { dist[tx][ty] = d + 1; q.emplace(tx, ty); } } tele[c].clear(); // 关键:清空,避免重复传送 } }

关键点解释

为什么用dist[nx][ny] > d + 1而不是dist[nx][ny] == INF

这是为了处理重复入队的情况。在某些情况下,一个格子可能通过不同路径多次被尝试访问,我们只保留最短距离。

为什么传送后要清空列表

这是算法的核心优化:

  • 第一次遇到字母c时,距离是d

  • 将所有字母c的格子标记为距离d+1

  • 之后如果再遇到字母c的其他格子,距离至少是d+1

  • 而d+1 ≥ d+1,不会产生更短路径

  • 清空后,后续遇到同字母格子时,tele[c]为空,不会进入循环

#include <bits/stdc++.h> using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int H, W; cin >> H >> W; vector<string> S(H); for (int i = 0; i < H; i++) { cin >> S[i]; } vector<vector<pair<int, int>>> tele(26); for (int i = 0; i < H; i++) { for (int j = 0; j < W; j++) { if (S[i][j] >= 'a' && S[i][j] <= 'z') { tele[S[i][j] - 'a'].emplace_back(i, j); } } } const int INF = 1e9; vector<vector<int>> dist(H, vector<int>(W, INF)); queue<pair<int, int>> q; dist[0][0] = 0; q.emplace(0, 0); const int dx[4] = {1, -1, 0, 0}; const int dy[4] = {0, 0, 1, -1}; while (!q.empty()) { auto [x, y] = q.front(); q.pop(); int d = dist[x][y]; if (x == H-1 && y == W-1) { cout << d << "\n"; return 0; } for (int k = 0; k < 4; k++) { int nx = x + dx[k]; int ny = y + dy[k]; if (nx >= 0 && nx < H && ny >= 0 && ny < W && S[nx][ny] != '#' && dist[nx][ny] > d + 1) { dist[nx][ny] = d + 1; q.emplace(nx, ny); } } if (S[x][y] >= 'a' && S[x][y] <= 'z') { int c = S[x][y] - 'a'; for (auto [tx, ty] : tele[c]) { if (dist[tx][ty] > d + 1) { dist[tx][ty] = d + 1; q.emplace(tx, ty); } } tele[c].clear(); } } cout << "-1\n"; return 0; }
http://www.jsqmd.com/news/539718/

相关文章:

  • 集合对象的绑定
  • 在Vscode中使用Claude code(接智普或KIMI)
  • MCP 服务开发笔记
  • Javascript提高:JavaScript Promise 超通俗解释-由Deepseek产生
  • 别再死记ResNet结构了!用PyTorch手把手复现ResNet34,搞懂残差连接为什么能解决‘退化’问题
  • 2026想申港大本科?专业港大本科申请中介推荐(附联系方式) - 品牌2026
  • C++的std--ranges适配器视图元素修改与原数据可变性在算法中的保证
  • AI 开发实战:异常处理怎么设计,AI 才能帮你真正找出薄弱点
  • CI2451实战指南:一款2.4G无线SoC芯片,如何让遥控玩具和灯控设计更简单?
  • 设置Linux命令行提示符shell prompt的前缀颜色,区分命令和输出结果(重连、重启都不会消失)
  • LuckyLilliaBot实战指南:从零构建NTQQ机器人系统
  • 天梯赛L2题解(029-032)
  • 像素幻梦创意工坊实战:为Unity游戏项目批量生成像素资源包
  • Markdown Viewer浏览器插件:快速预览Markdown文档的终极指南
  • 拖拽生成!这款编辑器做到了!告别代码妥妥的!
  • 下载 | Win11 25H2 官方正式版ISO映像!(3月更新、消费者版/专业版、商业版/企业版、26200.8037)
  • CSS 渐变的高级应用:色彩的流动艺术
  • 保姆级教程:用C语言数组手算1000的阶乘,解决PTA编程题(附完整代码)
  • 2026深圳美国留学申请中介推荐,高端美国留学中介服务流程与口碑盘点 - 品牌2026
  • 如何快速掌握茉莉花插件:面向中文文献管理者的终极Zotero优化指南
  • OpenClaw QQ 插件 v0.6.0 发布:率先适配OpenClaw新版本Plugin-SDK
  • 优麦云亚马逊营销云AMC功能与作用精准解析 | 最新优惠码速领 - 麦麦唛
  • 滚动轴承故障诊断系统设计:基于凯斯西储大学数据
  • 别等 Sora 了!一代神话陨落?OpenAI 这一手“弃车保帅”我看懂了...
  • 自适应模型预测控制在无人驾驶汽车轨迹跟踪中的应用
  • YOLO入门
  • 流式液相检测技术(CBA)研究进展
  • 做小月子要注意什么?科学修护指南
  • C++基础笔记(7):拷贝构造函数
  • 函数式编程的架构目标