胜利大逃亡之STL队列

因为自己远不如编译器聪明,所以要熟悉STL工具的使用。

读者也可以自行实现(循环)队列类。


4576 胜利大逃亡

题目描述 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


迷宫问题是很典型的搜索问题,当移动每一步的开销相同时,广度优先搜索可以搜索到最优解。

现在来分析题目的几个要求:

  1. 求从@到^的最短时间
  2. 如果从@到^有A,需要先找a
  3. 从@到^的最短时间比t长,不能离开

容易踩的几个坑:

  1. 如果从@到^有A,是否意味着求从@到a,再从a到^呢的最短时间呢?
  2. 走过了还可以再走一遍吗?

坑挖好了,就等着读者跳了。

不得不承认动态矩阵申请和分配很麻烦,题目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又被魔王抓走了)。 

可见,打印路线(前驱记录),支持斜方向,多维迷宫,多钥匙迷宫(可用二进制位标识状态)等都是很有意思的拓展问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值