BFS+状态标记

bfs跑图标记时,往往不会一个点去过一次就不能再去,只要当前状态不同,或者拥有更优状态(价值)时就可以重新去标记过的点

例一:推箱子

思路: 

我们尝试分解步骤:

1,如果只有箱子,箱子能不能去到终点(这样就是一道简单的bfs模板)

2,因为箱子每次往一个方向移动,所以人都需要跑去后面推动箱子——那么人能不能从当前位置去到箱子后面那个位置呢(又是一个bfs模板)

3,如果我们分开来考虑,一个点只要经过一次,但我们再合起来会发现,合起来箱子从不同方向到同一个点,人是在不同位置推的,所以标记应该是到达一个点+人推的方向vis[N][N][4]

#include <bits/stdc++.h>
using namespace std;
#define ll     long long
typedef unsigned long long ull;
typedef pair<long long, long long> pll;

//std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
const int INF = 0x3f3f3f3f;         //int型的INF
const ll llINF = 0x3f3f3f3f3f3f3f3f;//ll型的llINF
const int N = 10;

int a[N][N];
int w[5][3] = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}};
bool vis[N][N][5];//vis[a][b][i]表示以i方向去(b,a)位置是否进行过这个状态
bool vis1[N][N];//人找箱子普通标记就好
int n, m, sx, sy, rx, ry;

struct node
{
	int x, y, rx, ry, t; //对于每个状态的箱子,必须记录人上一次推完这个箱子后到达的位置
} head, tmp, hd, tp;

bool bfsr(int j)
{
	memset(vis1, 0, sizeof(vis1));
	queue<node>p;
	hd.x = head.rx, hd.y = head.ry;//人的初始位置就是bfs正在进行的head状态的人位置
	int x = head.x - w[j][0], y = head.y - w[j][1];//人需要去箱子箱子位置的后面
	p.push(hd);
	vis1[hd.x][hd.y] = 1;
	if (x >= 1 && x <= n && y >= 1 && y <= m && a[x][y] != 1) //人能去这个位置的话
		{
			while (!p.empty())
				{
					hd = p.front();
					p.pop();
					if (hd.x == x && hd.y == y)return 1;
					for (int i = 0; i < 4; ++i)
						{
							tp = hd;
							tp.x += w[i][0], tp.y += w[i][1];
							if (a[tp.x][tp.y] != 1 && tp.x >= 1 && tp.x <= n && tp.y >= 1 && tp.y <= m && !vis1[tp.x][tp.y])
								{
									vis1[tp.x][tp.y] = 1;
									p.push(tp);
								}
						}
				}
		}

	return 0;
}

void bfs()
{
	memset(vis, 0, sizeof(vis));
	head.x = sx, head.y = sy, head.rx = rx, head.ry = ry, head.t = 0;
	queue<node>q;
	q.push(head);
	while (!q.empty())
		{
			head = q.front();
			q.pop();
			if (a[head.x][head.y] == 3)
				{
					cout << head.t << endl;
					return;
				}
			for (int i = 0; i < 4; ++i)
				{
					tmp = head;
					tmp.x += w[i][0], tmp.y += w[i][1], tmp.t++;
					if (a[tmp.x][tmp.y] != 1 && tmp.x >= 1 && tmp.x <= n && tmp.y >= 1 && tmp.y <= m && !vis[tmp.x][tmp.y][i])
						{
							int h = a[head.x][head.y];
							a[head.x][head.y] = 1; //在bfs人是否可以去到箱子后面时,箱子是相当于墙的,人不能穿过箱子,所以先暂时改箱子为墙
							bool flag = bfsr(i); //i记录箱子移动方向,人便去反方向
							a[head.x][head.y] = h;
							if (flag)
								{
									vis[tmp.x][tmp.y][i] = 1;
									tmp.rx = head.x, tmp.ry = head.y; //人推完在原来箱子的位置
									q.push(tmp);
								}
						}
				}

		}
	cout << -1 << endl;
}

int main()
{
	int t;
	cin >> t;
	while (t--)
		{
			cin >> n >> m;
			for (int i = 1; i <= n; ++i)for (int j = 1; j <= m; ++j)
					{
						cin >> a[i][j];
						if (a[i][j] == 2)sx = i, sy = j;
						else if (a[i][j] == 4)rx = i, ry = j;
					}
			bfs();
		}
}

例二:胜利大逃亡(续)

思路:

当我们手上持有相同种类钥匙时,一个点毫无疑问只需要走一次,所以可以把钥匙状态二进制状态压缩

#include <bits/stdc++.h>
using namespace std;
#define ll     long long
typedef unsigned long long ull;
typedef pair<long long, long long> pll;

//std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
const int INF = 0x3f3f3f3f;         //int型的INF
const ll llINF = 0x3f3f3f3f3f3f3f3f;//ll型的llINF
const int N = 25;

char a[N][N];
int n, m, t, sx, sy;
bool vis[N][N][1100];
int w[5][3] = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}};

struct node
{
	int x, y, t, num;
} head, tmp;

void bfs()
{
	memset(vis, 0, sizeof(vis));
	queue<node>q;
	head.x = sx, head.y = sy, head.t = 0, head.num = 0;
	q.push(head);
vis[head.x][head.y][0]=1;
	while (!q.empty())
		{
			head = q.front();
			q.pop();
			if (a[head.x][head.y] == '^' && head.t < t)
				{
					cout << head.t << endl;
					return;
				}
			if (head.t + 1 < t)
				{
					for (int i = 0; i < 4; ++i)
						{
							tmp = head;
							tmp.x += w[i][0], tmp.y += w[i][1], tmp.t++;
							if (a[tmp.x][tmp.y] != '*' && tmp.x >= 1 && tmp.x <= n && tmp.y >= 1 && tmp.y <= m && !vis[tmp.x][tmp.y][tmp.num])
								{
									if (a[tmp.x][tmp.y] >= 'A' && a[tmp.x][tmp.y] <= 'Z')
										{
											int k = a[tmp.x][tmp.y] - 'A';//num的二进制第k位为1,表示有这把钥匙,我才可以进这个门
											if ((tmp.num >> k) & 1)
												{
													vis[tmp.x][tmp.y][tmp.num] = 1;
													q.push(tmp);
												}
										}
									else if (a[tmp.x][tmp.y] >= 'a' && a[tmp.x][tmp.y] <= 'z')
										{
											int k = a[tmp.x][tmp.y] - 'a';
											tmp.num |= (1 << k);//只有过钥匙点,一定拿到钥匙,所以状态改变一般不用+,而是直接num这个第k位用|运算保证是1
											if (!vis[tmp.x][tmp.y][tmp.num])//num更新,查询如果没有访问过,访问
												{
													vis[tmp.x][tmp.y][tmp.num] = 1;
													q.push(tmp);
												}
										}
									else
										{
											vis[tmp.x][tmp.y][tmp.num] = 1;
											q.push(tmp);
										}
								}
						}
				}

		}
	cout << -1 << endl;
}

int main()
{
	while (cin >> n >> m >> t)
		{
			for (int i = 1; i <= n; ++i)for (int j = 1; j <= m; ++j)
					{
						cin >> a[i][j];
						if (a[i][j] == '@')sx = i, sy = j;
					}
			bfs();
		}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值