从快吃中饭开始看题,一直到晚上七点半终于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 2≤N≤300,2≤M≤300。
迷宫里有一些传送装置,可以将奶牛从一点到另一点进行瞬间转移。这些装置可以双向使用。
如果一头奶牛处在这个装置的起点或者终点,这头奶牛就必须使用这个装置。
玉米迷宫除了唯一的一个出口都被玉米包围。
迷宫中的每个元素都由以下项目中的一项组成:
- 玉米,
#
表示,这些格子是不可以通过的。 - 草地,
.
表示,可以简单的通过。 - 传送装置,每一对大写字母 A \tt{A} A 到 Z \tt{Z} Z 表示。
- 出口,
=
表示。 - 起点,
@
表示
奶牛能在一格草地上可能存在的四个相邻的格子移动,花费 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 2∼N+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 个单位时间。
思路
这一题!我!看到题目!想都没想!就是双端队列!
但是!不行!!(一会儿对这一点解释一下)
以下是这一题的正解思路:
- 首先找到起点终点并记录下来
- 然后找到对应的传送门,可以用map<PII, PII>来存,要把对应的两个门都存起来,因为对应的两个传送门可以相互传送
- 最后最关键的就是开始BFS了:
先把起点存进队列,然后每次取出队头搜索
如果队头是终点,直接输出队头坐标的dist
即可
如果不是,那就遍历上下左右四个操作,删去不能走的路和不合法的位置这两种情况后,还会出现以下两种情况:
a.是传送门
那么!请注意!不能直接更新当前这个门!!!
因为走到这个门时直接传到另一边门了,是没办法在这个门停住的!
所以,更新另一边对应门的dist
和st
!
有同学可能会问那么当前点的dist
和st
怎么办呢?
我们考虑什么时候会在当前点停住呢?对啦,传到另一个点之后随便走一步再回来,就会回到当前这个点啦(太!坑!了!),当前这个点会在那个时候更新的
b.是普通草地
直接更新当前点dist
和st
即可
再说一下为什么不用双端队列呢?
看上面这个图
其实从点到下面的传送门,可以直接理解为从点到上面的传送门,不经过下面的传送门
这样每走一步的权重还是1,走不走传送门根本没有权重上的差别,也就不需要用双端队列了
接下来看AC代码吧~(虽然写的是双端队列但是只是因为懒得改掉,没有用双端队列特有的函数)
代码
#include <bits/stdc++.h>
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);
}