因为自己远不如编译器聪明,所以要熟悉STL工具的使用。
读者也可以自行实现(循环)队列类。
题目描述 Description
elfness被魔王抓走了,这次魔王把elfness关在一个n*m的地牢里。地牢的某个地方安装了一个带锁的门,钥匙藏在地牢的另外一个地方,elfness想要通过这个门,就必须先走到藏钥匙的地方取钥匙。刚开始的时候elfness被关在(sx,sy)的位置,而离开地牢的门在(ex,ey)的位置。elfness每分钟只能从一个位置走到相邻四个位置中的其中一个。魔王每t分钟都回地牢视察一次,若发现elfness不在原位置便会把他拎回去。经过若干次的尝试,elfness已经画出了整个地牢的地图。现在请你帮他计算能否再次成功逃亡。只要在魔王下次视察之前走到出口就算离开地牢,如果魔王回来的时候还未到出口都算逃亡失败。注意,逃跑路线不一定非要通过带锁的门。
输入描述 Input Description
第一行有三个整数n,m,t。接下来的n行m列为地牢的地图,其中包括:
. 代表路
* 代表墙
@ 代表elfness的起始位置
^ 代表地牢的出口
A 代表带锁的门
a 代表钥匙
输出描述 Output Description
一行,包含一个整数。对于可以成功逃亡的情况,请输出至少需要多少分钟才能离开,如果不能则输出 -1。
样例输入 Sample Input
4 4 100
@..A
a.*.
***.
^...
样例输出 Sample Output
11
数据范围及提示 Data Size & Hint
对于50%的数据,地牢没有带锁的门和钥匙
对于100%的数据,2<=n,m<=20,t>0
迷宫问题是很典型的搜索问题,当移动每一步的开销相同时,广度优先搜索可以搜索到最优解。
现在来分析题目的几个要求:
- 求从@到^的最短时间
- 如果从@到^有A,需要先找a
- 从@到^的最短时间比t长,不能离开
容易踩的几个坑:
- 如果从@到^有A,是否意味着求从@到a,再从a到^呢的最短时间呢?
- 走过了还可以再走一遍吗?
坑挖好了,就等着读者跳了。
不得不承认动态矩阵申请和分配很麻烦,题目mn的范围是2到20,输入开个21*21矩阵map足够了。类似的,我们再开个21*21矩阵visited方便标识我们是否走过。所在的坐标是一个点,为方便点的移动,我们建立一个4*2的方向矩阵dir。
#define N 21
#define M 21
char map[M][N];
int visited[M][N];
int dir[4][2] = { { -1,0 },{ 0,-1 },{ 0,1 },{ 1,0 } };
好吧,还是要解答挖的第二个坑,当我们没有钥匙时,门是障碍。当我们有钥匙时,门是路。有些点是可以访问两遍的,那就是去拿钥匙的路上一遍,拿到钥匙再一遍。visited中的元素应该有3种状态,分别是未访问,没有钥匙时访问,拿到钥匙后访问。只要不是障碍的未访问的点,都可以访问。没有钥匙时访问的点,拿到钥匙后是可以访问的,相反则不行(读者可以自己思考)。
我们再写一个点类Point,是队列中存储的元素。除了存储xy坐标以外,还存储着当前时间和是否拿到钥匙。如果这个点已经带有钥匙,由从这个点走到他相邻的点时,也应该带着钥匙(若之前没有钥匙,到新的点有可能得到钥匙),并且花费了单位时间。
struct Point {
int x;
int y;
int gotkey;
int step;
Point(int x, int y, int gotkey, int step) :x(x), y(y), gotkey(gotkey), step(step) {}
};
在广度优先搜索中,一开始除了起点所有的点都未访问。至于起点有多少个并不重要,全部都存入队列。队列的特点是先入队(push)列的先出队(pop),和栈相反,但不代表栈得不到可行解,但是栈得到的不一定是最优解(读者可以自己思考和实现)。
当队列不为空(empty)时,取队首(front)元素p。若p是终点,说明找到了最优解。若p不是终点,那么尝试往这个点相邻的点前进。每走到一个未访问的点(不是障碍),就入队,访问所有可能的得到钥匙,只有访问过钥匙才能访问门,因此门可能被访问多次。若最后队列为空,意味着无解。
#include<iostream>
#include<queue>
using namespace std;
#define N 21
#define M 21
char map[M][N];
int visited[M][N];
int dir[4][2] = { { -1,0 },{ 0,-1 },{ 0,1 },{ 1,0 } };
struct Point {
int x;
int y;
int gotkey;
int step;
Point(int x, int y, int gotkey, int step) :x(x), y(y), gotkey(gotkey), step(step) {}
};
int BFS(int m, int n, char start, char end, char key, char door, char ob) {
queue<Point> q;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (map[i][j] == start) {
q.push(Point(i, j, 0, 0));
visited[i][j] = -1;
}
else visited[i][j] = 0;
}
}
int gotkey = 0;
while (!q.empty()) {
Point p = q.front();
q.pop();
int step = p.step, gotkey = p.gotkey;
if (map[p.x][p.y] == end) return step;
for (int k = 0; k < 4; k++) {
int i = p.x, j = p.y;
i += dir[k][0];
j += dir[k][1];
if (i >= m || i<0 || j >= n || j<0) continue;
char c = map[i][j];
if (c == ob) continue;
if (gotkey) {
if (visited[i][j] == 2) continue;
visited[i][j] = 2;
}
else {
if (visited[i][j] || c == door) continue;
if (c == key) gotkey = 1;
visited[i][j] = 1;
}
q.push(Point(i, j, gotkey, step + 1));
}
}
return 0;
}
int main() {
int m, n, max;
cin >> m >> n >> max;
for (int i = 0; i < m; i++) cin >> map[i];
int step = BFS(m, n, '@', '^', 'a', 'A', '*');
if (step&&step <= max) cout << step << endl;
else cout << "-1" << endl;
return 0;
}
迷宫问题可以玩出很多花样(elfness又被魔王抓走了)。
可见,打印路线(前驱记录),支持斜方向,多维迷宫,多钥匙迷宫(可用二进制位标识状态)等都是很有意思的拓展问题。