算法刷题【洛谷P1825_USACO11OPEN】Corn Maze S
2022-04-17
题目描述
(跳过英文版,想看的请去原题)
去年秋天,奶牛们去参观了一个玉米迷宫,迷宫里有一些传送装置,可以将奶牛从一点到另一点进行瞬间转移。这些装置可以双向使用:一头奶牛可以从这个装置的起点立即到此装置的终点,同时也可以从终点出发,到达这个装置的起点。如果一头奶牛处在这个装置的起点或者终点,这头奶牛就必须使用这个装置。
玉米迷宫的外部完全被玉米田包围,除了唯一的一个出口。
这个迷宫可以表示为N×M的矩阵(2 ≤ N ≤ 300; 2 ≤ M ≤ 300),矩阵中的每个元素都由以下项目中的一项组成:
玉米,这些格子是不可以通过的。
草地,可以简单的通过。
一个装置的结点,可以将一头奶牛传送到相对应的另一个结点。
出口
奶牛仅可以在相邻两个格子之间移动,要在这两个格子不是由玉米组成的前提下才可以移动。奶牛能在一格草地上可能存在的四个相邻的格子移动。从草地移动到相邻的一个格子需要花费一个单位的时间,从装置的一个结点到另一个结点需要花费0个单位时间。
被填充为玉米的格子用“#”表示,草地用“.”表示,每一对装置的结点由相同的大写字母组成“A-Z”,且没有两个不同装置的结点用同一个字母表示,出口用“=”表示。
Bessie在这个迷宫中迷路了,她知道她在矩阵中的位置,将Bessie所在的那一块草地用“@”表示。求出Bessie需要移动到出口处的最短时间。
例如以下矩阵,N=5,M=6:
###=##
#.W.##
#.####
#.@W##
######唯一的一个装置的结点用大写字母W表示。
最优方案为:先向右走到装置的结点,花费一个单位时间,再到装置的另一个结点上,花费0个单位时间,然后再向右走一个,再向上走一个,到达出口处,总共花费了3个单位时间。
输入格式
第一行:两个用空格隔开的整数N和M;
第2-N+1行:第i+1行描述了迷宫中的第i行的情况(共有M个字符,每个字符中间没有空格。)
输出格式
一个整数,表示Bessie到达终点所需的最短时间。
输入输出样例
In 1:
5 6
###=##
#.W.##
#.####
#.@W##
######Out 1:
3题解
不多解释了,区别在于别人的题解大多是在发现节点为字母后再去逐个遍历找另一头的,我在输入时处理了
有一个坑参考这个题解前面的解释!
https://www.luogu.com.cn/blogs/201425/solution-p1825
#include <bits/stdc++.h>
using namespace std;
int n, m, sx, sy, ex, ey;
char mp[301][301];
int send[301][301][3], hadLetter[100][3];
short mv[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
struct node
{
int x, y, step;
};
queue<node> que;
int main()
{
cin >> n >> m;
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
cin >> mp[i][j];
switch (mp[i][j])
{
case '@':
sx = i;
sy = j;
break;
case '=':
ex = i;
ey = j;
break;
}
if (mp[i][j] >= 'A' && mp[i][j] <= 'Z')
{
if (hadLetter[mp[i][j]][0] == 1) // 如果这个字母出现过了
{
send[i][j][0] = 1; // 点(i,j)存在传送关系
send[i][j][1] = hadLetter[mp[i][j]][1]; // 传送的目标点的x坐标
send[i][j][2] = hadLetter[mp[i][j]][2]; // 传送的目标点的y坐标
send[hadLetter[mp[i][j]][1]][hadLetter[mp[i][j]][2]][0] = 1; // 该字母上一次出现时的点也存在传送关系
send[hadLetter[mp[i][j]][1]][hadLetter[mp[i][j]][2]][1] = i; // 传送的目标点的x坐标
send[hadLetter[mp[i][j]][1]][hadLetter[mp[i][j]][2]][2] = j; // 传送的目标点的y坐标
}
else
{
hadLetter[mp[i][j]][0] = 1; // 这个字母出现过了
hadLetter[mp[i][j]][1] = i; // 该字母第一次出现时(也就是这次)的x坐标
hadLetter[mp[i][j]][2] = j; // y坐标
}
}
}
}
node t;
t.x = sx;
t.y = sy;
t.step = 0;
que.push(t);
mp[sx][sy] = '#';
int x, y;
while (!que.empty())
{
node q = que.front();
que.pop();
for (int i = 0; i < 4; i++)
{
x = q.x + mv[i][0];
y = q.y + mv[i][1];
if (x >= 0 && y >= 0 && x < n && y < m && mp[x][y] != '#')
{
/*
到点(x,y)有两种方式,走过去or传送过去
所以普通点接受走一次,而有传送关系的接受两种方式各走一次
所以每个点只需要标注是否以“走过去”这个方法到达过
这样对于关联传送关系的点也就等于标记了另一个端点被通过传送方式到过了
如果你脑子够用,补充说明见程序最后
*/
mp[x][y] = '#';
if (x == ex && y == ey)
{
cout << q.step + 1;
return 0;
}
if (send[x][y][0])
{
int xx = x, yy = y;
x = send[xx][yy][1];
y = send[xx][yy][2];
// mp[x][y] = '#';
}
t.x = x;
t.y = y;
t.step = q.step + 1;
que.push(t);
}
}
}
return 0;
}补充说明:
设a,b两点存在传送关系
∵传送为强制
∴走到a的最小步数实则为到达b的最小步数,反之亦然
∴锁住a这个点的实际意义是我们到过了b,因为锁住后就不可能再到达b了
同时对于直接被传送到b的情况而言,这一次踩到a是无意义的,无法从a继续向周围走
而有意义的抵达a应是先踩到b然后被传送过来
所以b这个点暂时仍不能标记(如果标记了就不可能有意义地到达a了)
而a仍然要标记,否则会出现在更多步数的时候再次访问b