题目相关
题目链接
AtCoder Beginner Contest 184 E 题,https://atcoder.jp/contests/abc184/tasks/abc184_e。
Problem Statement
There is a town represented as a two-dimensional grid with H horizontal rows and W vertical columns.
A character ai,j describes the square at the i-th row from the top and j-th column from the left. Here, ai,j is one of the following: S
, G
, .
, #
, a
, ..., and z
.#
represents a square that cannot be entered, and a
, ..., z
represent squares with teleporters.
Takahashi is initially at the square represented as S
. In each second, he will make one of the following moves:
- Go to a non-
#
square that is horizontally or vertically adjacent to his current position. - Choose a square with the same character as that of his current position, and teleport to that square. He can only use this move when he is at a square represented as
a
, ..., orz
.
Find the shortest time Takahashi needs to reach the square represented as G
from the one represented as S
.
If the destination is unreachable, report -1
instead.
Input
Input is given from Standard Input in the following format:
H W
a1,1 ... a1,w
.
.
.
ah,1 ... ah,w
Output
Print the shortest time Takahashi needs to reach the square represented as G
from the one represented as S
.
If the destination is unreachable from the initial position, print -1
instead.
Samples1
Sample Input 1
2 5
S.b.b
a.a.G
Sample Output 1
4
Explaination
Let (i,j) denote the square at the i-th row from the top and j-th column from the left.
Initially, Takahashi is at (1,1). One way to reach (2,5) in four seconds is:
- go from (1,1) to (2,1);
- teleport from (2,1) to (2,3), which is also an
a
square; - go from (2,3) to (2,4);
- go from (2,4) to (2,5).
Samples2
Sample Input 2
11 11
S##...#c...
...#d.#.#..
..........#
.#....#...#
#.....bc...
#.##......#
.......c..#
..#........
a..........
d..#...a...
.#........G
Sample Output 2
14
Samples3
Sample Input 3
11 11
.#.#.e#a...
.b..##..#..
#....#.#..#
.#dd..#..#.
....#...#e.
c#.#a....#.
.....#..#.e
.#....#b.#.
.#...#..#..
......#c#G.
#..S...#...
Sample Output 3
-1
Constraints
- 1≤H,W≤2000
- ai,j is
S
,G
,.
,#
, or a lowercase English letter. There is exactly one square represented asS
and one square represented asG
.
题解报告
题目翻译
有一个城镇可以用二维网格表示,该城镇水平方向有 H 行,垂直方向有 W 列。字符 描述最上边开始的第 i 行最左面开始的第 j 列,它的值是:S,G,.,#,a,...,z 其中一个。# 表示这个点不能进入,小写字母 a ~ z 表示传送点。
高桥开始的的位置为 S,他每次可以按照以下规则移动:
- 向相邻的位置移动,只要这个位置不是 #。
- 如果这里是传送点,高桥可以传送到字母相同的传送点。
请找出高桥从 S 到 G 的最短路径,如果不存在,输出 -1。
题目分析
看完题目描述,走迷宫并且是找出最短路线,那么必然使用 BFS。本题和 BFS 模板相比,多了一个传送点,如何解决传送点问题。根据题目意思,我们一共有四种走法,即上、右、下、左。
我的思路是这样的:
1、在某个点判断下一个位置的时候,不考虑下一个点是否是传送点,和原有的 BFS 一样进行移动。
2、所有下一个可能位置计算完成后,判断这个点是否是传送点。如果是传送点,遍历修改传送点的代价 cost。修改结束,清空所有传送点。
下面我们使用样例数据来验证一下思路。
样例数据 1 分析
根据样例数据,起点在 (1,1) 位置。一般算法竞赛都将左上角定义为 1,1。起点的代价 cost 为 0,其余点的 cost 都为 INT_MAX。我们按照上、右、下、左进行遍历。首先将起点 (1,1) 加入到 BFS 的遍历队列中。下面我们开始模拟整个 BFS 遍历过程。
初始状态
cost 数组的值为
1 | 2 | 3 | 4 | 5 | |
1 | 0 | INT_MAX | INT_MAX | INT_MAX | INT_MAX |
2 | INT_MAX | INT_MAX | INT_MAX | INT_MAX | INT_MAX |
第一轮 BFS 遍历队列
BFS 遍历队列不为空,获取队首位置,得到 (1,1),并删除该数据,我们从 (1,1) 开始遍历。目前的代价为 0,从这里出发到下一个位置的代价将为 2。
(1,1) 向上
新位置为 (1,0),走出了迷宫,不是合法位置。忽略。
(1,1) 向右
新位置为 (2,1),是合法位置,而且不是 #,可以走。当前 cost[2][1]=INT_MAX,更新 cost[2][1]=1,并吧 (2,1) 加入到 BFS 的遍历队列,此时 BFS 的遍历队列只有一个元素,即 (2,1)。
(1,1) 向下
新位置为 (1,2),是合法位置,而且不是 #,可以走。当前 cost[1][2]=INT_MAX,更新 cost[1][2]=1,并吧 (1,2) 加入到 BFS 的遍历队列,此时 BFS 的遍历队列只有两个元素,即 (2,1)、(1,2)。
(1,1) 向左
新位置为 (0,1),走出了迷宫,不是合法位置。忽略。
判断 (1,1) 是不是传送点
当前位置 (1,1) 不是传送点。
第一轮 BFS 遍历结束
cost 数组的值为
1 | 2 | 3 | 4 | 5 | |
1 | 0 | 1 | INT_MAX | INT_MAX | INT_MAX |
2 | 1 | INT_MAX | INT_MAX | INT_MAX | INT_MAX |
第二轮 BFS 遍历队列
BFS 遍历队列不为空,获取队首位置,得到 (2,1),并删除该数据,我们从 (2,1) 开始遍历。目前的代价为 1,从这里出发到下一个位置的代价将为 2。
(2,1) 向上
新位置为 (2,0),走出了迷宫,不是合法位置。忽略。
(2,1) 向右
新位置为 (3,1),走出了迷宫,不是合法位置。忽略。
(2,1) 向下
新位置为 (2,2),是合法位置,而且不是 #,可以走。当前 cost[2][2]=INT_MAX,更新 cost[2][2]=2,并吧 (2,2) 加入到 BFS 的遍历队列,此时 BFS 的遍历队列只有三个元素,即 (1,2)、(2,2)。
(2,1) 向左
新位置为 (1,1),是合法位置,而且不是 #,可以走。当前 cost[1][1]=0,而我们移动的代价是 2,所以忽略。
判断 (2,1) 是不是传送点
当前位置 (2,1) 的值为 a,是传送点。遍历迷宫中所有值为 a 的传送点,可以传送的位置有 (2,3),将这个点的 cost 都改为 2,也就意味着 (1,1)->(3,2) 的代价是 2,同时将着两点加入到 BFS 队列中。此时 BFS 的遍历队列只有四个元素,即 (1,2)、(2,2)、(2,3)。同时我们清空所有 a 的传送点。
第二轮 BFS 遍历结束
cost 数组的值为
1 | 2 | 3 | 4 | 5 | |
1 | 0 | 1 | INT_MAX | INT_MAX | INT_MAX |
2 | 1 | 2 | 2 | INT_MAX | INT_MAX |
第三轮 BFS 遍历队列
BFS 遍历队列不为空,获取队首位置,得到 (1,2),并删除该数据,我们从 (1,2) 开始遍历。目前已经的代价是 1,从这里出发到下一个位置的代价将为 2。
(1,2) 向上
新位置为 (1,1),是合法位置,而且不是 #,可以走。当前 cost[1][2]=0,而我们移动的代价是 2,就是从 (1,2) 移动到 (1,1) 的代价是 2。所以忽略。
(1,2) 向右
新位置为 (2,2),是合法位置,而且不是 #,可以走。新的路径代价没有缩小,忽略。说明我们不管是从 (1,1)->(2,1)->(2,2) 或者 (1,1)->(1,2)->(2,2) 的代价都是一样的。
(1,2) 向下
新位置为 (1,3),是合法位置,而且不是 #,可以走。当前 cost[1][3]=INT_MAX,更新 cost[1][3]=2,并吧 (1,3) 加入到 BFS 的遍历队列,此时 BFS 的遍历队列只有三个元素,即 (2,2)、(2,3)、(1,3)。
(1,2) 向左
新位置为 (0,2),走出了迷宫,不是合法位置。忽略。
判断 (1,2) 是不是传送点
当前位置不是传送点。
第三轮 BFS 遍历结束
cost 数组的值为
1 | 2 | 3 | 4 | 5 | |
1 | 0 | 1 | 2 | INT_MAX | INT_MAX |
2 | 1 | 2 | 2 | INT_MAX | INT_MAX |
第四轮 BFS 遍历队列
BFS 遍历队列不为空,获取队首位置,得到 (2,2),并删除该数据,我们从 (2,2) 开始遍历。目前已经的代价是 2,从这里出发到下一个位置的代价将为 3。
(2,2) 向上
新位置为 (2,1),是合法位置,而且不是 #,可以走。当前 cost[2][1]=1,而我们移动的代价是 3。所以忽略。
(2,2) 向右
新位置为 (3,2),走出了迷宫,不是合法位置。忽略。
(2,2) 向下
新位置为 (2,3),是合法位置,而且不是 #,可以走。当前 cost[2][3]=2,而我们移动的代价是 3。所以忽略。
(2,2) 向左
新位置为 (1,2),是合法位置,而且不是 #,可以走。当前 cost[1][2]=1,而我们移动的代价是 3。所以忽略。
判断 (2,2) 是不是传送点
当前位置不是传送点。
第四轮 BFS 遍历结束
cost 数组的值为
1 | 2 | 3 | 4 | 5 | |
1 | 0 | 1 | 2 | INT_MAX | INT_MAX |
2 | 1 | 2 | 2 | INT_MAX | INT_MAX |
第五轮 BFS 遍历队列
BFS 遍历队列不为空,获取队首位置,得到 (2,3),并删除该数据,我们从 (2,3) 开始遍历。目前已经的代价是 2,从这里出发到下一个位置的代价将为 3。
(2,3) 向上
新位置为 (2,2),是合法位置,而且不是 #,可以走。当前 cost[2][2]=2,而我们移动的代价是 3。所以忽略。
(2,3) 向右
新位置为 (3,3),走出了迷宫,不是合法位置。忽略。
(2,3) 向下
新位置为 (2,4),是合法位置,而且不是 #,可以走。当前 cost[2][4]=INT_MAX,更新 cost[2][4]=3,并吧 (2,4) 加入到 BFS 的遍历队列,此时 BFS 的遍历队列只有三个元素,即 (1,3)、(2,4)。
(2,3) 向左
新位置为 (1,3),是合法位置,而且不是 #,可以走。当前 cost[1][3]=2,而我们移动的代价是 3。所以忽略。
判断 (2,3) 是不是传送点
当前位置是传送点。但是这个传送点已经处理了,上面我们处理的时候,将这个队列清空。
第五轮 BFS 遍历结束
cost 数组的值为
1 | 2 | 3 | 4 | 5 | |
1 | 0 | 1 | 2 | INT_MAX | INT_MAX |
2 | 1 | 2 | 2 | 3 | INT_MAX |
第六轮 BFS 遍历队列
BFS 遍历队列不为空,获取队首位置,得到 (1,3),并删除该数据,我们从 (1,3) 开始遍历。目前已经的代价是 2,从这里出发到下一个位置的代价将为 3。
(1,3) 向上
新位置为 (1,2),是合法位置,而且不是 #,可以走。当前 cost[1][2]=1,而我们移动的代价是 3。所以忽略。
(1,3) 向右
新位置为 (2,3),是合法位置,而且不是 #,可以走。当前 cost[2][3]=2,而我们移动的代价是 3。所以忽略。
(1,3) 向下
新位置为 (1,4),是合法位置,而且不是 #,可以走。当前 cost[1][4]=INT_MAX,更新 cost[1][4]=3,并吧 (2,4) 加入到 BFS 的遍历队列,此时 BFS 的遍历队列只有三个元素,即 (2,4)、(1,4)。
(1,3) 向左
新位置为 (0,3),走出了迷宫,不是合法位置。忽略。
判断 (1,3) 是不是传送点
当前位置是传送点 b。我们可以从这里传送到 (1,5)。当前 cost[1][5]=INT_MAX,更新 cost[1][5]=3,并吧 (1,5) 加入到 BFS 的遍历队列,此时 BFS 的遍历队列只有三个元素,即 (2,4)、(1,4)、(1,5)。清空传送点 b。
第六轮 BFS 遍历结束
cost 数组的值为
1 | 2 | 3 | 4 | 5 | |
1 | 0 | 1 | 2 | 3 | 3 |
2 | 1 | 2 | 2 | 3 | INT_MAX |
第七轮 BFS 遍历队列
BFS 遍历队列不为空,获取队首位置,得到 (2,4),并删除该数据,我们从 (2,4) 开始遍历。目前已经的代价是 3,从这里出发到下一个位置的代价将为 4。
(2,4) 向上
新位置为 (2,3),是合法位置,而且不是 #,可以走。当前 cost[2][3]=2,而我们移动的代价是 4。所以忽略。
(2,4) 向右
新位置为 (3,4),走出了迷宫,不是合法位置。忽略。
(2,4) 向下
新位置为 (2,5),是合法位置,而且不是 #,可以走。当前 cost[2][5]=INT_MAX,更新 cost[2][5]=4,并吧 (2,5) 加入到 BFS 的遍历队列,此时 BFS 的遍历队列只有三个元素,即 (1,4)、(1,5)、(2,5)。
(2,4) 向左
新位置为 (1,4),是合法位置,而且不是 #,可以走。当前 cost[1][4]=3,而我们移动的代价是 4。所以忽略。
判断 (2,4) 是不是传送点
当前位置不是传送点。
第七轮 BFS 遍历结束
cost 数组的值为
1 | 2 | 3 | 4 | 5 | |
1 | 0 | 1 | 2 | 3 | 3 |
2 | 1 | 2 | 2 | 3 | 4 |
第八轮 BFS 遍历队列
BFS 遍历队列不为空,获取队首位置,得到 (1,4),并删除该数据,我们从 (1,4) 开始遍历。目前已经的代价是 3,从这里出发到下一个位置的代价将为 4。
(1,4) 向上
新位置为 (1,3),是合法位置,而且不是 #,可以走。当前 cost[1][3]=2,而我们移动的代价是 4。所以忽略。
(1,4) 向右
新位置为 (2,4),是合法位置,而且不是 #,可以走。当前 cost[2][4]=3,而我们移动的代价是 4。所以忽略。
(1,4) 向下
新位置为 (1,5),是合法位置,而且不是 #,可以走。当前 cost[1][5]=4,而我们移动的代价是 4。所以忽略。
(1,4) 向左
新位置为 (0,4),走出了迷宫,不是合法位置。忽略。
判断 (1,4) 是不是传送点
当前位置不是传送点。
第八轮 BFS 遍历结束
cost 数组的值为
1 | 2 | 3 | 4 | 5 | |
1 | 0 | 1 | 2 | 3 | 3 |
2 | 1 | 2 | 2 | 3 | 4 |
第九轮 BFS 遍历队列
BFS 遍历队列不为空,获取队首位置,得到 (1,5),并删除该数据,我们从 (1,5) 开始遍历。目前已经的代价是 3,从这里出发到下一个位置的代价将为 4。
(1,5) 向上
新位置为 (1,4),是合法位置,而且不是 #,可以走。当前 cost[1][4]=3,而我们移动的代价是 4。所以忽略。
(1,5) 向右
新位置为 (2,5),是合法位置,而且不是 #,可以走。当前 cost[2][5]=4,而我们移动的代价是 4。所以忽略。
(1,5) 向下
新位置为 (1,6),走出了迷宫,不是合法位置。忽略。
(1,5) 向左
新位置为 (0,5),走出了迷宫,不是合法位置。忽略。
判断 (1,5) 是不是传送点
当前位置是传送点,但已经处理过。
第九轮 BFS 遍历结束
cost 数组的值为
1 | 2 | 3 | 4 | 5 | |
1 | 0 | 1 | 2 | 3 | 3 |
2 | 1 | 2 | 2 | 3 | 4 |
第十轮 BFS 遍历队列
BFS 遍历队列不为空,获取队首位置,得到 (2,5),并删除该数据,我们从 (2,5) 开始遍历。目前已经的代价是 4,从这里出发到下一个位置的代价将为 5。
(2,5) 向上
新位置为 (2,4),是合法位置,而且不是 #,可以走。当前 cost[2][4]=3,而我们移动的代价是 5。所以忽略。
(2,5) 向右
新位置为 (3,5),走出了迷宫,不是合法位置。忽略。
(2,5) 向下
新位置为 (2,6),走出了迷宫,不是合法位置。忽略。
(2,5) 向左
新位置为 (1,5),是合法位置,而且不是 #,可以走。当前 cost[1][5]=3,而我们移动的代价是 5。所以忽略。
判断 (2,5) 是不是传送点
当前位置是传送点,但已经处理过。
第十轮 BFS 遍历结束
cost 数组的值为
1 | 2 | 3 | 4 | 5 | |
1 | 0 | 1 | 2 | 3 | 3 |
2 | 1 | 2 | 2 | 3 | 4 |
到这里为止,BFS 队列为空,说明我们遍历了迷宫的所有点。遍历完成。
注意:只要 BFS 第一次走到终点,BFS 就可以停止了,因为 BFS 可以保证第一次就是最短路径。
AC 参考代码
//https://atcoder.jp/contests/abc184/tasks/abc184_e
//E - Third Avenue
#include <bits/stdc++.h>
using namespace std;
//如果提交到OJ,不要定义 __LOCAL
//#define __LOCAL
const int MAXH=2e3+4;
const int MAXW=2e3+4;
char maze[MAXH][MAXW];
int cost[MAXH][MAXW];
int h,w;
pair<int, int> qd;
pair<int, int> zd;
vector<pair<int, int>> tele[26];
const int dx[]={0, 1, 0, -1};
const int dy[]={-1, 0, 1, 0};
int bfs() {
queue<pair<int, int>> q;
q.push(qd);
cost[qd.first][qd.second]=0;
while (true!=q.empty()) {
pair<int, int> cur = q.front();
q.pop();
int nc=cost[cur.first][cur.second]+1;//下一个位置的代价
//新位置
for (int i=0; i<4; i++) {
int nx = cur.first+dx[i];
int ny = cur.second+dy[i];
//合法性判断
if (nx<1||nx>h||ny<1||ny>w||'#'==maze[nx][ny]) {
continue;
}
//路径判断
if (nc<cost[nx][ny]) {
cost[nx][ny]=nc;
q.emplace(nx, ny);
//判断是不是终点
if (nx==zd.first && ny==zd.second) {
return nc;
}
}
}
//如果是传送点
if (maze[cur.first][cur.second]>='a' && maze[cur.first][cur.second]<='z') {
//将所有的
vector<pair<int, int>> &t=tele[maze[cur.first][cur.second]-'a'];
vector<pair<int, int>>::iterator it;
for (it=t.begin(); it!=t.end(); it++) {
if (nc<cost[it->first][it->second]) {
cost[it->first][it->second]=nc;
q.emplace(it->first, it->second);
}
}
t.clear();
}
}
return -1;
}
int main() {
#ifndef __LOCAL
//这部分代码需要提交到OJ,本地调试不使用
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
#endif
cin>>h>>w;
for (int i=1; i<=h; i++) {
for (int j=1; j<=w; j++) {
cost[i][j]=INT_MAX;
cin>>maze[i][j];
if ('S'==maze[i][j]) {
//起点
qd.first=i;
qd.second=j;
cost[i][j]=0;
} else if ('G'==maze[i][j]) {
zd.first=i;
zd.second=j;
} else if (maze[i][j]>='a' && maze[i][j]<='z') {
tele[maze[i][j]-'a'].emplace_back(i, j);
}
}
}
//开始BFS
cout<<bfs()<<"\n";
#ifdef __LOCAL
//这部分代码不需要提交到OJ,本地调试使用
system("pause");
#endif
return 0;
}
时间复杂度
O(HW)。
空间复杂度
O(HW)。