BFS 题目整理 POJ2251、HDU1429、POJ3182、USACO06、UVA10047 The Monocycle

BFS

POJ 2251 Dungeon Master

题意

有一个三维监狱,每一步可以向东,向西,向北,向南,向上,向下移动
‘S’为起点,'E’为出口,’#‘为墙,’.'为路

分析

找到起点,直接bfs即可

代码

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cstring>
using namespace std;
#pragma warning (disable:4996)
const int maxn = 32;
int k, n, m;
char a[maxn][maxn][maxn];
bool vis[maxn][maxn][maxn];	//记录是否经过
struct Node	//存储点
{
	int k;
	int i;
	int j;
	int s;	//时间
	Node(int k, int i, int j, int s) :k(k), i(i), j(j), s(s){}
};
queue<Node> q;
int main() {
	int _k[6] = { 0,0,0,0,-1,1 },
		_i[6] = { -1,0,1,0,0,0 },
		_j[6] = { 0,-1,0,1,0,0 };	//移动方案
	while (~scanf("%d%d%d", &k, &n, &m) && k + n + m) {
		int si = 0, sj = 0, sk = 0, ei = 0, ej = 0, ek = 0;
		for (int K = 1; K <= k; K++) {
			for (int i = 1; i <= n; i++) {
				scanf("%s", a[K][i] + 1);
				for (int j = 1; j <= m; j++) {
					if (a[K][i][j] == 'S')
						sk = K, si = i, sj = j;
				}
			}
		}
		while (!q.empty())q.pop();
		q.push(Node(sk, si, sj, 0));
		vis[sk][si][sj] = true;
		int dk, di, dj, ans; bool flag = false;
		while (!q.empty()) {
			if (flag)break;
			Node x = q.front(); q.pop();
			for (int i = 0; i < 6; i++) {
				dk = x.k + _k[i];
				di = x.i + _i[i];
				dj = x.j + _j[i];
				if (dk<1 || dk>k || di<1 || di>n || dj<1 || dj>m)
					continue;
				if (vis[dk][di][dj])
					continue;
				if (a[dk][di][dj] == '#')
					continue;
				vis[dk][di][dj] = true;
				q.push(Node(dk, di, dj, x.s + 1));
				if (a[dk][di][dj] == 'E') {
					flag = true;
					ans = x.s + 1;
					break;
				}
			}
		}
		if (flag) printf("Escaped in %d minute(s).\n", ans);
		else printf("Trapped!\n");
		memset(a, 0, sizeof(a));
		memset(vis, 0, sizeof(vis));
	}
}

HDU1429 胜利大逃亡(续)

bfs走回路

题意

有一副迷宫,在 ( &lt; t ) (&lt;t) (<t)的时间内走到终点
. 代表路

  • 代表墙
    @ 代表Ignatius的起始位置
    ^ 代表地牢的出口
    A-J 代表带锁的门,对应的钥匙分别为a-j
    a-j 代表钥匙,对应的门分别为A-J

分析

不同于普通的bfs
有10把钥匙:考虑用bool数组,bitset,状压(即一个小于1024的数表示拿钥匙的情况,每个二进制位表示一把钥匙)
显然用状压更加灵活
走回路:由于会出现先去拿钥匙,再原路返回去开门的情况,所以在记录是否走过该点时要有所变通
一般是将vis数组改成记录一个值或者增加一维表示状态
显然通过一个点持有钥匙的情况有多种,只记录一种钥匙的情况是不够的,所以我们增加一维表示拿钥匙的情况
加下来就是普通的bfs

代码

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cstring>
using namespace std;
#pragma warning (disable:4996)
char s[30][30];
bool vis[30][30][1024];
struct Node
{
	int i;
	int j;
	int key;
	int s;
	Node(int i,int j,int k,int s):i(i),j(j),key(k),s(s){}
};
queue<Node> q;
int main() {
	int n, m, t;
	int si = 0, sj = 0;
	int _i[4] = { -1,0,1,0 },
		_j[4] = { 0,-1,0,1 };
	while (~scanf("%d%d%d", &n, &m, &t)) {
		for (int i = 1; i <= n; i++) {
			scanf("%s", s[i] + 1);
			for (int j = 1; j <= m; j++)
				if (s[i][j] == '@')
					si = i, sj = j;
		}
		q.push(Node(si, sj, 0, 0));
		vis[si][sj][0] = true;
		int di, dj, ans;
		bool flag = false;
		while (!q.empty()) {
			if (flag)break;
			Node x = q.front(); q.pop();
			if (x.s == t - 1)break;
			for (int i = 0; i < 4; i++) {
				di = x.i + _i[i];
				dj = x.j + _j[i];
				if (di<1 || di>n || dj<1 || dj>m)
					continue;
				if (s[di][dj] == '*')
					continue;
				if (s[di][dj] >= 'A' && s[di][dj] <= 'J')
					if (!(x.key >> (s[di][dj] - 'A') & 1))
						continue;
				int key = x.key;
				if (s[di][dj] >= 'a' && s[di][dj] <= 'j')
					key |= (1 << (s[di][dj] - 'a'));
				if (vis[di][dj][key])
					continue;
				vis[di][dj][key] = true;
				q.push(Node(di, dj, key, x.s + 1));
				if (s[di][dj] == '^') {
					flag = true;
					ans = x.s + 1;
					break;
				}
			}
		}
		memset(vis, 0, sizeof(vis));
		if (flag) printf("%d\n", ans);
		else  printf("-1\n");
		while (!q.empty())q.pop();
	}
}

双向广搜

对于给定了起始状态和终止状态,且正逆搜索均能够实现的情况
将起始状态和终止状态同时作为起点进行 BFS,相对于单向 BFS 可以减少相当一部分搜索量
对于扩展结点较多,而目标结 点又处在深处的情况有很好的优化效果

我们将起点和终点同时加入队列进行 BFS
对于起点和终点扩展 出的点我们打上不同标记
当一个点即将打上第二个标记的时候 说明双向搜索相遇,输出步数之和即可

while (qx.size()) {
	int x = qx.front(), y = qy.front();
	qx.pop(), qy.pop();
	for (int i = 0; i < 8; i++) {
		int nx = x + dx[i], ny = y + dy[i];
		if (nx >= n || ny >= n || nx < 0 || ny < 0) continue;
		if (vis[nx][ny]) {
			if (vis[nx][ny] == vis[x][y]) continue;
			return step[x][y] + step[nx][ny] + 1;
		}
		step[nx][ny] = step[x][y] + 1;
		vis[nx][ny] = vis[x][y];
		qx.push(nx), qy.push(ny);
	}
}

POJ3182 The Grove

[USACO06JAN]树林The Grove

题意

给出一张图,从起点出发,将‘x’连通块圈住,返回起点,所需最小步数

分析

不同于其他bfs,本题所需从起点出发回到起点

射线法:为了防止不经过圈,就回到起点

  • 从要圈的部分任意一点引一条向下的射线,必须要穿过射线一次
    回到原点才是完成圈地
  • 为防止反复穿过射线,我们需要对出穿过射线做一个方向限制
    如果当前在射线上,且企图抵达反向穿越射线的点则 continue

代码

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
using namespace std;
#pragma warning (disable:4996)
const int maxn = 55;
struct Node
{
	int x;
	int y;
	int s;
	bool cross;
	Node(int x, int y, bool cross, int s) :x(x), y(y), cross(cross), s(s) {}
};
queue <Node> q;
char s[maxn][maxn];
bool mark[maxn][maxn], vis[maxn][maxn][2];
int n, m, _X, _Y;
void MarkLine() {		//画出射线
	bool flag = true;
	for (int i = 1; i <= n && flag; i++)
		for (int j = 1; j <= m && flag; j++)
			if (s[i][j] == 'X'){
				_X = i, _Y = j;
				flag = false;
				break;
			}
	for (int i = _X - 1; i >= 1; i--)
		mark[i][_Y] = true;
}
int main() {
	int _x[8] = { -1,0,1,0,-1,-1,1,1 },
		_y[8] = { 0,-1,0,1,-1,1,-1,1 };
	int sx, sy;
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; i++) {
		scanf("%s", s[i] + 1);
		for (int j = 1; j <= m; j++)
			if (s[i][j] == '*')
				sx = i, sy = j;
	}
	MarkLine();
	q.push(Node(sx, sy, false, 0));
	vis[sx][sy][0] = true;
	int dx, dy, ans; bool flag = false, g;
	while (!q.empty()) {
		if (flag) break;
		Node x = q.front(); q.pop();
		for (int k = 0; k < 8; k++) {
			dx = x.x + _x[k];
			dy = x.y + _y[k];
			g = x.cross | mark[dx][dy];
			if (dx<1 || dx>n || dy<1 || dy>m)
				continue;
			if (s[dx][dy] == 'X')
				continue;
			if (vis[dx][dy][g])
				continue;
			if (mark[dx][dy] && x.y >= _Y)	//只能从左向右经过射线
				continue;
			if (mark[x.x][x.y] && dy <= _Y)	//经过射线向右移动
				continue;
			vis[dx][dy][g] = true;
			q.push(Node(dx, dy, g, x.s + 1));
			if (s[dx][dy] == '*') {
				ans = x.s + 1;
				flag = true;
				break;
			}
		}
	}
	printf("%d\n", ans);
}

UVA10047 The Monocycle

题意

独轮车是一种仅有一个轮子的特殊自行车。他的轮子被等分成5个扇形,分别涂上一种不同的颜色。现在有一个人骑自行车行驶在M*N的网格平面上。每个格子的大小恰好使得当车从一个格子骑到下一个格子时,轮子恰好转过一个扇形。

如下图所示,当轮子在1号格子的中心时,蓝色扇形的外弧线中线刚好于地面接触。当它移动到下一个格子(2号格子)的时候,白色扇形的外弧线于地面接触。

有些各自中有障碍,所以车子不能通过这些格子。骑车人从某个格子出发,希望用最短的时间移动到目标格。在任何一个格子上,他要么骑到下一个格子,要么左转或者右转90度。其中每项动作恰好需要1秒来完成。初始时,他面朝北且绿色扇形贴着地面。到达目标格时,也必须是绿色扇形贴着地面,但朝向无限制。如下图所示。

分析

显然的bfs,不同于其他bfs就是参数比较多
码量不大,仔细一点就好

代码

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cstring>
using namespace std;
#pragma warning (disable:4996)
int n, m;
char a[30][30];
bool vis[30][30][4][5];
struct Node
{
	int x;
	int y;
	int s;
	int c;
	int d;
	Node(int x,int y,int d,int c,int s):x(x),y(y),d(d),c(c),s(s){}
};
queue<Node>q;
int main() {
	int sx, sy, g = 0;
	int _x[6] = { -1,0,1,0,0,0 },
		_y[6] = { 0,1,0,-1,0,0 },
		_d[6] = { 0,0,0,0,1,-1 },
		_c[6] = { 1,1,1,1,0,0 };
	while (~scanf("%d%d", &n, &m) && n + m) {
		memset(vis, 0, sizeof(vis));
		for (int i = 1; i <= n; i++) {
			scanf("%s", a[i] + 1);
			for (int j = 1; j <= m; j++)
				if (a[i][j] == 'S')
					sx = i, sy = j;
		}
		q.push(Node(sx, sy, 0, 0, 0));
		vis[sx][sy][0][0] = true;
		int dx, dy, dd, dc, ans;
		bool flag = false;
		while (!q.empty()) {
			if (flag)break;
			Node x = q.front(); q.pop();
			for (int k = 3; k < 6; k++) {
				if (k == 3) {
					dx = x.x + _x[x.d];
					dy = x.y + _y[x.d];
					dd = x.d;
					dc = (x.c + _c[x.d]) % 5;
				}
				else {
					dx = x.x;
					dy = x.y;
					dd = (x.d + _d[k] + 4) % 4;
					dc = x.c;
				}
				if (dx<1 || dx>n || dy<1 || dy>m)
					continue;
				if (vis[dx][dy][dd][dc])
					continue;
				if (a[dx][dy] == '#')
					continue;
				vis[dx][dy][dd][dc] = true;
				q.push(Node(dx, dy, dd, dc, x.s + 1));
				if (a[dx][dy] == 'T' && dc == 0) {
					flag = true;
					ans = x.s + 1;
					break;
				}                      
			}
		}
		while (!q.empty())q.pop();
		if (g)printf("\n");
		printf("Case #%d\n", ++g);
		if (flag)printf("minimum time = %d sec\n", ans);
		else printf("destination not reachable\n");
	}
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
POJ2251是一个经典的题目,也被称为"水仙花的谜题"。该题目要求在一个三维的迷宫中找到从起点到终点的最短路径。 在这个题目中,迷宫由一个3D的数组表示,每个位置上的值代表了该位置的状态。其中,0表示可以通过的路径,1表示墙壁,2表示起点,3表示终点。你需要编写一个程序来找到从起点到终点的最短路径,并输出路径的长度。 解决这个问题的一种常见方法是使用广度优先搜索BFS)算法。BFS算法可以从起点开始,逐层遍历迷宫中的位置,直到找到终点或者遍历完所有可达位置。在遍历过程中,你需要记录每个位置的距离和路径信息,以便找到最短路径。 以下是解决该问题的大致思路: 1. 定义一个队列,将起点加入队列,并标记起点已访问。 2. 使用循环来遍历队列中的元素,直到队列为空。 3. 在循环中,取出队列中的元素,并获取其相邻可达位置。 4. 对于每个相邻位置,判断是否为终点,如果是则输出最短路径长度并结束程序。 5. 如果不是终点,则判断该位置是否为可通过的路径,并且未被访问过。如果满足条件,则将该位置加入队列,并更新距离和路径信息。 6. 重复步骤2-5,直到找到终点或者遍历完所有可达位置。 这只是一个简单的介绍,实际解决该问题还需要考虑一些细节,比如如何表示迷宫、如何判断位置的合法性等。你可以在编写代码时参考相关的算法和数据结构知识。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值