题目说明:
小Q被邪恶的大魔王困在了迷宫里,love8909决定去解救她。迷宫里面有一些陷阱,一旦走到陷阱里,就会被困身亡:(,迷宫里还有一些古老的传送阵,一旦走到传送阵上,会强制被传送到传送阵的另一头。
现在请你帮助love8909算一算,他至少需要走多少步才能解救到小Q? (上下左右四个方向走,同一个传送门可以多次使用)
输入:第一行为一个整数T,表示测试数据组数。每组测试数据第一行为两个整数N,M,(1 <= N, M <= 50)表示迷宫的长和宽。接下来有N行,每行M个字符,是迷宫的具体描述。
'.'表示安全的位置,'#'表示陷阱,
'Q'表示小Q的位置,'L'表示love8909所在位置,
数据保证love8909只有一个,数据也保证小Q只有一个。小写字母'a'-'z'表示分别表示不同的传送阵,数据保证传送阵两两配对。
输出:每组数据输出一行,解救小Q所需的最少步数,如果无论如何都无法救小Q,输出-1。
样例:
2
5 5
....L
.###.
b#b#a
##.##
...Qa
5 5
....L
.###.
.#.#.
##.##
...Q.输出:
3
-1
题解思路:由题目我们不难看出这题是 BFS 类题目 小 L 到 小 Q的 最短距离 我们应该先有以下思路
1.如何去存储地图
2.如何存储传送点 和 读取传送点
3.如果去使用 bfs(广度优先搜索) 并利用它得到最后的答案 (如果不会,可以先去学习,本文将粗略讲解 )
4.坑项: 传送点可以被多次使用,我们该如何标记防止重复踩 或者 踩不到
第一点地图的存储:我们可以开一个 N * N 大小的 char数组(根据题目 N > 50即可)
同时我们也可以把需要的数据全开了 这里我们采用临链表的方式对 传送的存储与查找 一下是作者开的变量(可能存在冗余)
坐标的点可以用 pair存储<i, j>
typedef pair<int,int> PII;
const int N = 60;
char g[N][N];//地图
int d[N][N]; //表示 小L到 点 g[i][j]的最短距离
int n, m;//n 层 m 列
int a[2][2];//a[0] a[1]分别存储 小L 和 小Q 的坐标
int h[26], ne[N * N], idx; //h用于连接临链表 idx
int ch[N][N];//对走过的点进行标记
PII q[N * N], e[N * N]; //q用于存储走过的点 e存储传送点坐标
第二: 我们在输入数据的同时 我们也可以进行查找 并存储 (传送点 和 小Q 小 L)
for (int i = 1; i <= n; i++)
{
scanf("%s", g[i] + 1);//从 g[1] 开始取
for (int j = 1; j <= m; j++)
{
if (g[i][j] >= 'a' && g[i][j] <= 'z')//传送点的存储 这里采用的是 临链表的方式
{
//将字母转换为 26 个数字 每个字母占要给位子
int x = g[i][j] - 'a';
//存储当前 字母的 坐标 第idx个字母占第idx的位置
e[idx] = { i, j };
//链表的连接: 将h[x]的尾接到 第idx的尾节点
ne[idx] = h[x];
//将h[x]的尾与 第idx 的头链接
h[x] = idx++;
continue;
}
if (g[i][j] == 'L')//存储 小L
{
a[0][0] = i;
a[0][1] = j;
}
if (g[i][j] == 'Q')//存储 小Q
{
a[1][0] = i;
a[1][1] = j;
}
}
}
而读取传送点我们就需要用到外接的函数了 这里我设了一个find函数 (查找并返回坐标点)
PII find(int x, int y)//传入的是 传送点的坐标
{
int tmp = g[x][y] - 'a';
for (int i = h[tmp]; i != -1; i = ne[i])
{
auto t = e[i];
if (t.first != x || t.second != y)//如果有一个值不同 则说明是 不同传送点 返回传送点
{
return t;//输出的也是传送点的坐标(另一个)
}
}
}
由题目可知 传送点 以一组为单位出现 并且不会出现多个 可以用这种 判断直接找
接下来我们要对地图进行bfs搜索 (下面这段代码是 作者第一次写的错误写法)
int bfs()
{
int hh = 0, tt = 0;
memset(d, -1, sizeof d);
q[0] = {a[0][0], a[0][1]};
d[a[0][0]][a[0][1]] = 0;
int dx[] = {0,1,0,-1}, dy[] = {1,0,-1,0};
while(hh <= tt)
{
auto t = q[hh++];
for(int i = 0; i < 4; i++)
{
int x = t.first + dx[i], y = t.second + dy[i];
if(x >= 1 && x <= n && y >= 1 && y <= m && g[x][y] != '#' && d[x][y] == -1)
{
if(g[x][y] >='a' && g[x][y] <= 'z')
{
d[x][y] = d[t.first][t.second] + 1;
auto tmp = find(x , y);
x = tmp.first;
y = tmp.second;
}
d[x][y] = d[t.first][t.second] + 1;
q[++tt] = {x , y};
}
}
}
return d[a[1][0]][a[1][1]];
}
如果我们单纯用走过的距离d[i][j]去判断是否走过,这样会漏掉,返回的方向 (用其他方式也是可以的,作者这里采用的是另取一个数组来判断是否走过) 就比如以下数据
3 3
LaQ
.a#
###
这样的数据用上面的代码是 -1 明显是错误的(答案应该是 3)
那我们该怎么办呢
我们可以确定的是每个 点 只能走一次 所以作者这里采用的是 对每个点进行标记
如果进入了某个点查找 则进行标记 ch[i][j]
int bfs()
{
//这里设 hh . tt 为了方便元素的进队与出队
int hh = 0, tt = 0;
memset(d, -1, sizeof d);//初始化 d (距离)和 ch(判断是否走过)
memset(ch, 0, sizeof ch);
//q用于存储每次遍历点的坐标 首先将 L的坐标存入 q
q[0] = { a[0][0], a[0][1] };
d[a[0][0]][a[0][1]] = 0; //起始点坐标距离是 0
int dx[] = { 0,1,0,-1 }, dy[] = { 1,0,-1,0 }; //对四个方向进行查找
//当hh <= tt 时说明队列中还有元素
while (hh <= tt)
{
auto t = q[hh++];
//点进入 这个点正在被查找 要将进行标记 ch[i][j] = 1
ch[t.first][t.second] = 1;
for (int i = 0; i < 4; i++)
{
int x = t.first + dx[i], y = t.second + dy[i];
if (x >= 1 && x <= n && y >= 1 && y <= m && g[x][y] != '#' && ((ch[x][y] == 0 && (g[x][y] <= 'a' || g[x][y] >= 'z')) || (g[x][y] >= 'a' && g[x][y] <= 'z' && ch[find(x, y).first][find(x, y).second] == 0)))
//这里又臭又长的 是为了 元素的正确性
//简单说就是 防止 小L 走到陷阱 走出边界 走路重复
{
if (g[x][y] >= 'a' && g[x][y] <= 'z')
{
//如果查找到字母 就找到相应的 传送点坐标 并重新赋值
auto tmp = find(x, y);
x = tmp.first;
y = tmp.second;
}
//这里是为了防止重复走(传送门可多次走 可能会发生重复 应该取最小值)
if (d[x][y] != -1)
{
d[x][y] = min(d[x][y], d[t.first][t.second] + 1);
}
//正常则将 上一个点的距离 + 1
else d[x][y] = d[t.first][t.second] + 1;
q[++tt] = { x , y };
}
}
}
//最后返回最终的坐标 所需的距离 如果到不了 则是 -1 最开始已经初始化过了
return d[a[1][0]][a[1][1]];
}
这里的关于点是否能走的问题 我 用分了两种情况:
第一:如果是非传送点 则仅仅只用考虑该点的ch[i][j]是否为 0即可
第二 : 如果是传送点 则需要考虑 传送点的另一个点 是否为0
(ch[x][y] == 0 && (g[x][y] <= 'a' || g[x][y] >= 'z')) //条件1
(g[x][y] >= 'a' && g[x][y] <= 'z' && ch[find(x, y).first][find(x, y).second] == 0)//条件2
到这里 基本上整体架构思路已经讲清楚了
综合代码奉上:
#include <iostream>
#include<algorithm>
#include <vector>
#include <stdlib.h>
#include <cstring>
#include <queue>
#include <cmath>
using namespace std;
typedef pair<int, int> PII;
const int N = 60;
char g[N][N];
int d[N][N]; //分别表示地图 和 距离
int n, m, h[N * N], ne[N * N], idx, a[2][2], ch[N][N]; //a[0] L
PII q[N * N], e[N * N]; //q 存储 走过的 e存储传送点坐标
PII find(int x, int y)
{
int tmp = g[x][y] - 'a';
for (int i = h[tmp]; i != -1; i = ne[i])
{
auto t = e[i];
if (t.first != x || t.second != y)
{
return t;
}
}
}
int bfs()
{
int hh = 0, tt = 0;
memset(d, -1, sizeof d);
memset(ch, 0, sizeof ch);
q[0] = { a[0][0], a[0][1] };
d[a[0][0]][a[0][1]] = 0;
int dx[] = { 0,1,0,-1 }, dy[] = { 1,0,-1,0 };
while (hh <= tt)
{
auto t = q[hh++];
ch[t.first][t.second] = 1;
for (int i = 0; i < 4; i++)
{
int x = t.first + dx[i], y = t.second + dy[i];
if (x >= 1 && x <= n && y >= 1 && y <= m && g[x][y] != '#' && ((ch[x][y] == 0 && (g[x][y] <= 'a' || g[x][y] >= 'z')) || (g[x][y] >= 'a' && g[x][y] <= 'z' && ch[find(x, y).first][find(x, y).second] == 0)))
{
if (g[x][y] >= 'a' && g[x][y] <= 'z')
{
auto tmp = find(x, y);
x = tmp.first;
y = tmp.second;
}
if (d[x][y] != -1)
{
d[x][y] = min(d[x][y], d[t.first][t.second] + 1);
}
else d[x][y] = d[t.first][t.second] + 1;
q[++tt] = { x , y };
}
}
}
return d[a[1][0]][a[1][1]];
}
int main()
{
int t;
cin >> t;
while (t--)
{
idx = 0;
memset(e, -1, sizeof e);
memset(ne, -1, sizeof ne);
memset(h, -1, sizeof h);
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
{
scanf("%s", g[i] + 1);
for (int j = 1; j <= m; j++)
{
if (g[i][j] >= 'a' && g[i][j] <= 'z')//传送点的存储
{
int x = g[i][j] - 'a';
e[idx] = { i, j };
ne[idx] = h[x];
h[x] = idx++;
continue;
}
if (g[i][j] == 'L')
{
a[0][0] = i;
a[0][1] = j;
}
if (g[i][j] == 'Q')
{
a[1][0] = i;
a[1][1] = j;
}
}
}
cout << bfs() << endl;
}
}
这是作者第一份博客,如果有说不好的地方请见谅qwq 也可以给作者提出些意见
如果有问题 可以与作者交流 一起学习进步 |wp