【洛谷】p1825 [USACO11OPEN] Corn Maze S

从快吃中饭开始看题,一直到晚上七点半终于AC了!!!!!!!!!

写篇题解记录一下这个激动人心的时刻

文章目录

  • 题目:[USACO11OPEN] Corn Maze S
    • 题面翻译
    • 题目描述
    • 输入格式
    • 输出格式
    • 样例 #1
      • 样例输入 #1
      • 样例输出 #1
    • 提示
  • 思路
  • 代码

题目:[USACO11OPEN] Corn Maze S

题面翻译

奶牛们去一个 N × M N\times M N×M 玉米迷宫, 2 ≤ N ≤ 300 , 2 ≤ M ≤ 300 2 \leq N \leq 300,2 \leq M \leq300 2N300,2M300

迷宫里有一些传送装置,可以将奶牛从一点到另一点进行瞬间转移。这些装置可以双向使用。

如果一头奶牛处在这个装置的起点或者终点,这头奶牛就必须使用这个装置。

玉米迷宫除了唯一的一个出口都被玉米包围。

迷宫中的每个元素都由以下项目中的一项组成:

  1. 玉米,# 表示,这些格子是不可以通过的。
  2. 草地,. 表示,可以简单的通过。
  3. 传送装置,每一对大写字母 A \tt{A} A Z \tt{Z} Z 表示。
  4. 出口,= 表示。
  5. 起点, @ 表示

奶牛能在一格草地上可能存在的四个相邻的格子移动,花费 1 1 1 个单位时间。从装置的一个结点到另一个结点不花时间。

题目描述

This past fall, Farmer John took the cows to visit a corn maze. But this wasn’t just any corn maze: it featured several gravity-powered teleporter slides, which cause cows to teleport instantly from one point in the maze to another. The slides work in both directions: a cow can slide from the slide’s start to the end instantly, or from the end to the start. If a cow steps on a space that hosts either end of a slide, she must use the slide.

The outside of the corn maze is entirely corn except for a single exit.

The maze can be represented by an N x M (2 <= N <= 300; 2 <= M <= 300) grid. Each grid element contains one of these items:

* Corn (corn grid elements are impassable)

* Grass (easy to pass through!)

* A slide endpoint (which will transport a cow to the other endpoint)

* The exit

A cow can only move from one space to the next if they are adjacent and neither contains corn. Each grassy space has four potential neighbors to which a cow can travel. It takes 1 unit of time to move from a grassy space to an adjacent space; it takes 0 units of time to move from one slide endpoint to the other.

Corn-filled spaces are denoted with an octothorpe (#). Grassy spaces are denoted with a period (.). Pairs of slide endpoints are denoted with the same uppercase letter (A-Z), and no two different slides have endpoints denoted with the same letter. The exit is denoted with the equals sign (=).

Bessie got lost. She knows where she is on the grid, and marked her current grassy space with the ‘at’ symbol (@). What is the minimum time she needs to move to the exit space?

输入格式

第一行:两个用空格隔开的整数 N N N M M M

2 ∼ N + 1 2\sim N+1 2N+1 行:第 i + 1 i+1 i+1 行描述了迷宫中的第 i i i 行的情况(共有 M M M个字符,每个字符中间没有空格)。

输出格式

一个整数,表示起点到出口所需的最短时间。

样例 #1

样例输入 #1

5 6
###=##
#.W.##
#.####
#.@W##
######

样例输出 #1

3

提示

例如以下矩阵, N = 5 , M = 6 N=5,M=6 N=5,M=6

###=##
#.W.##
#.####
#.@W##
######

唯一的一个装置的结点用大写字母 W \tt{W} W 表示。

最优方案为:先向右走到装置的结点,花费一个单位时间,再到装置的另一个结点上,花费 0 0 0 个单位时间,然后再向右走一个,再向上走一个,到达出口处,总共花费了 3 3 3 个单位时间。

思路

这一题!我!看到题目!想都没想!就是双端队列!

但是!不行!!(一会儿对这一点解释一下)

以下是这一题的正解思路:

  1. 首先找到起点终点并记录下来
  2. 然后找到对应的传送门,可以用map来存,要把对应的两个门都存起来,因为对应的两个传送门可以相互传送
  3. 最后最关键的就是开始BFS了:
    先把起点存进队列,然后每次取出队头搜索
    如果队头是终点,直接输出队头坐标的dist即可
    如果不是,那就遍历上下左右四个操作,删去不能走的路和不合法的位置这两种情况后,还会出现以下两种情况:
    a.是传送门
    那么!请注意!不能直接更新当前这个门!!!
    因为走到这个门时直接传到另一边门了,是没办法在这个门停住的!
    所以,更新另一边对应门的distst
    有同学可能会问那么当前点的distst怎么办呢?
    我们考虑什么时候会在当前点停住呢?对啦,传到另一个点之后随便走一步再回来,就会回到当前这个点啦(太!坑!了!),当前这个点会在那个时候更新的
    b.是普通草地
    直接更新当前点distst即可

再说一下为什么不用双端队列呢?
【洛谷】p1825 [USACO11OPEN] Corn Maze S_第1张图片
看上面这个图

其实从点到下面的传送门,可以直接理解为从点到上面的传送门,不经过下面的传送门
这样每走一步的权重还是1,走不走传送门根本没有权重上的差别,也就不需要用双端队列了

接下来看AC代码吧~(虽然写的是双端队列但是只是因为懒得改掉,没有用双端队列特有的函数)

代码

#include 

using namespace std;

typedef pair<int, int> PII;
#define ft first 
#define sd second 

const int N = 27, M = 1010;
const int inf = 0x3f3f3f3f;

int dx[4] = {1, -1, 0, 0};
int dy[4] = {0, 0, 1, -1};

int n, m;
int sx, sy, ex, ey;
char g[M][M];
bool st[M][M];
map<PII, PII> door;
int dist[M][M];

PII search_door(int x, int y, char a)
{
    for (int i = x; i < n; i ++ )
        for (int j = 0; j < m; j ++ )
            if (g[i][j] == a && !(i == x && j == y)) return {i, j};
    return {-1, -1}; // 返回-1说明没搜到对应的门,这个门就看成草地
}

int bfs(int sx, int sy)
{
    deque<PII> q;
    q.push_back({sx, sy});
    dist[sx][sy] = 0;

    while (q.size())
    {
        auto t = q.front();
        q.pop_front();

        int x = t.ft, y = t.sd;

        // 搜到终点
        if (x == ex && y == ey) return dist[x][y];

        for (int i = 0; i < 4; i ++ )
        {
            int a = x + dx[i], b = y + dy[i];
            
            if (a < 0 || b < 0 || a >= n || b >= m) continue;
            if (g[a][b] == '#') continue;

            if (door.count({a, b})) // 当前点是传送门
            {
                int c = door[{a, b}].ft, d = door[{a, b}].sd; // 对应门的坐标
                if (st[c][d]) continue;

                st[c][d] = true;
                q.push_back({c, d});
                dist[c][d] = dist[x][y] + 1;
            }
            else
            {
                if (st[a][b]) continue;

                st[a][b] = true;
                q.push_back({a, b});
                dist[a][b] = dist[x][y] + 1;
            }
        }
    }
    return -1;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    cin >> n >> m;
    for (int i = 0; i < n; i ++ ) cin >> g[i];

    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < m; j ++ )
            dist[i][j] = inf;

    // 起点终点
    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < m; j ++ )
            if (g[i][j] == '@') sx = i, sy = j; // 起点
            else if (g[i][j] == '=') ex = i, ey = j; // 终点
    
    // 传送门
    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < m; j ++ )
            if (g[i][j] >= 'A' && g[i][j] <= 'Z')
                if (!door.count({i, j})) // 这一对门没被标记过
                {
                    PII temp = search_door(i, j, g[i][j]); // 找另外一个门
                    if (temp != make_pair(-1, -1))
                    {
                        // 两个门都标记
                        door[{i, j}] = temp;
                        door[temp] = {i, j};
                    }
                }

    cout << bfs(sx, sy);
}

你可能感兴趣的:(洛谷,题解,算法,广度优先)