AtCoder题解 —— AtCoder Beginner Contest 184 —— E - Third Avenue - BFS

题目相关

题目链接

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, ..., or z.

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 as S and one square represented as G.

题解报告

题目翻译

有一个城镇可以用二维网格表示,该城镇水平方向有 H 行,垂直方向有 W 列。字符 a_{i,j} 描述最上边开始的第 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 数组的值为

 12345
10INT_MAXINT_MAXINT_MAXINT_MAX
2INT_MAXINT_MAXINT_MAXINT_MAXINT_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 数组的值为

 12345
101INT_MAXINT_MAXINT_MAX
21INT_MAXINT_MAXINT_MAXINT_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 数组的值为

 12345
101INT_MAXINT_MAXINT_MAX
2122INT_MAXINT_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 数组的值为

 12345
1012INT_MAXINT_MAX
2122INT_MAXINT_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 数组的值为

 12345
1012INT_MAXINT_MAX
2122INT_MAXINT_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 数组的值为

 12345
1012INT_MAXINT_MAX
21223INT_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 数组的值为

 12345
101233
21223INT_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 数组的值为

 12345
101233
212234

第八轮 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 数组的值为

 12345
101233
212234

第九轮 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 数组的值为

 12345
101233
212234

第十轮 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 数组的值为

 12345
101233
212234

到这里为止,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)。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
AtCoder Beginner Contest 134 是一场 AtCoder 的入门级比赛,以下是每道的简要题解: A - Dodecagon 目描述:已知一个正十二边形的边长,求它的面积。 解思路:正十二边形的内角为 $150^\circ$,因此可以将正十二边形拆分为 12 个等腰三角形,通过三角形面积公式计算面积即可。 B - Golden Apple 目描述:有 $N$ 个苹果和 $D$ 个盘子,每个盘子最多可以装下 $2D+1$ 个苹果,求最少需要多少个盘子才能装下所有的苹果。 解思路:每个盘子最多可以装下 $2D+1$ 个苹果,因此可以将苹果平均分配到每个盘子中,可以得到最少需要 $\lceil \frac{N}{2D+1} \rceil$ 个盘子。 C - Exception Handling 目描述:给定一个长度为 $N$ 的整数序列 $a$,求除了第 $i$ 个数以外的最大值。 解思路:可以使用两个变量 $m_1$ 和 $m_2$ 分别记录最大值和次大值。遍历整个序列,当当前数不是第 $i$ 个数时,更新最大值和次大值。因此,最后的结果应该是 $m_1$ 或 $m_2$ 中较小的一个。 D - Preparing Boxes 目描述:有 $N$ 个盒子和 $M$ 个物品,第 $i$ 个盒子可以放入 $a_i$ 个物品,每个物品只能放在一个盒子中。现在需要将所有的物品放入盒子中,每次操作可以将一个盒子内的物品全部取出并分配到其他盒子中,求最少需要多少次操作才能完成任务。 解思路:首先可以计算出所有盒子中物品的总数 $S$,然后判断是否存在一个盒子的物品数量大于 $\lceil \frac{S}{2} \rceil$,如果存在,则无法完成任务。否则,可以用贪心的思想,每次从物品数量最多的盒子中取出一个物品,放入物品数量最少的盒子中。因为每次操作都会使得物品数量最多的盒子的物品数量减少,而物品数量最少的盒子的物品数量不变或增加,因此这种贪心策略可以保证最少需要的操作次数最小。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

努力的老周

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值