[优先队列bfs/双端队列bfs]题目阵列

Description

进入集训队前,集训队大佬想跟你玩一个游戏,要是你提前完成了这个游戏就更有机会提前进入集训队,这个游戏是这样的:

这个游戏在一个题目阵列上进行,这个题目阵列由n*m的矩阵构成,每个单元格有a(i,j)道题目,单元格的题目存在只有三种情况:0,1,+∞,现在你初始时在(1,1)处,你每次可以向上下左右四个方向走,但是不允许越界,你每走到一个格子必须做完当前格子上的题目才能继续前进,这些题目的难度都是相等的,每道题你都需要花费x的时间来完成,现在询问你最少需要花费多少时间才能抵达迷宫出口(n,m)

Input

第一行给出三个正整数n,m,x(1≤n,m,x≤5000)

接下来有n行,每行m个数字,数字只有三种0,1,2分别表示格子的题目数为0,1,+∞

注意:本题输入量较大,请避免使用 std::cin 进行读入!

Output

如果可以在有限的时间到达终点那么输出需要花费的时间,如果不能则输出No Solution.(注意有点)

Sample Input 1 

5 6 1
1 0 2 0 0 1
1 1 0 0 2 0
2 1 2 1 1 0
0 0 1 2 0 0
1 2 1 1 1 1

Sample Output 1

4

Sample Input 2 

5 6 1
1 0 2 0 0 1
1 1 0 0 2 0
2 1 2 1 1 0
0 0 1 2 0 2
1 2 1 1 2 1

Sample Output 2

No Solution.

题意: 给出一个矩阵,需要从(1, 1)走到(n, m),如果该点有题目需要花费x秒,如果没题目就不需要花费时间,如果有无数道题代表该点不能走,询问从起点到终点的最短时间。

分析: 比较容易地想到需要用bfs,但是普通bfs不可用,因为对于四个方向的点,很可能先遍历有一道题的点,之后通过没有题的点又可以走到它,此时vis已经打上了标记,就没法以更小的花费来更新该点了。考虑bfs的本质,实际上就是一个dijkstra算法,对于每次操作从未被标记的点集中找出距离起点最近的点,加入集合打上标记,并以该点更新四周的点,重复操作直到找到终点。(当然由于bfs问题中给出的无向图比较特殊,所以有些地方可以针对性的优化一下,因此具体实现和dijkstra并不完全相同。)了解到这点后可以发现之前普通bfs的做法肯定是错误的,而正解双端队列/优先队列的做法也呼之欲出了。先考虑优先队列做法:按照上面bfs的本质操作即可,只是找到距离最近的点可以通过优先队列来实现,实际上优先队列bfs可以解决所有bfs问题,不过会比较慢罢了。之后考虑双端队列做法:还是要用到bfs的本质,每次都要找到距离起点最近的点,即距离起点最近的点在队头。为了满足这一要求,当遇到有题目的点(边权>0)时肯定不能加入到队头,毕竟还可能有没题目的点,这时应把它加入到队尾,而遇到了没题目的点(边权=0)一定要加入到队头,假如放到队尾也是不符合bfs本质的,但是放到了队头后,队列前半部分的点dis值都等于当前点dis值,后半部分的点dis值都大于当前点dis值,下次取队头时一定会取到距离起点最近的点。

最后总结一下双端队列与优先队列在bfs时的优缺点:

优先队列优点在于能解决任何bfs问题,缺点在于比较慢(体现在出入队的logn上)。

双端队列优点在于速度比较快,可以解决普通bfs无法解决的问题(例如本题),缺点在于只能解决一类特定问题(图上只有两种边权,且其中一种为0)。

具体代码如下:

优先队列:

        纯dijkstra堆优化版:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <queue>
#define inf 0x3f3f3f3f
using namespace std;
//本质上是dijkstra堆优化 
//会稍慢一些
int n, m, k, mp[5005][5005], _next[4][2] = {1, 0, 0, 1, -1, 0, 0, -1}, dis[5005][5005];
bool vis[5005][5005];
struct node
{
	int x, y, time;
};

struct cmp
{
	bool operator()(const node &a, const node &b)const
	{
		return a.time > b.time;
	}
};

signed main()
{
	scanf("%d%d%d", &n, &m, &k);
	for(int i = 1; i <= n; i++)
		for(int j = 1; j <= m; j++)
		{
			scanf("%d", &mp[i][j]);
			dis[i][j] = inf;
		}
	if(mp[1][1] == 2 || mp[n][m] == 2)
	{
		puts("No Solution.");
		return 0;
	}
	priority_queue<node, vector<node>, cmp> q;
	bool flag = false;
	long long ans;
	dis[1][1] = mp[1][1];
	q.push({1, 1, mp[1][1]});
	while(!q.empty())
	{
		node now = q.top();
		q.pop();
		if(now.x == n && now.y == m)
		{
			ans = now.time;
			flag = true;
			break;
		}
		if(vis[now.x][now.y])
			continue;
		vis[now.x][now.y] = true; 
		for(int i = 0; i <= 3; i++)
		{
			int nextx = now.x+_next[i][0], nexty = now.y+_next[i][1];
			if(nextx >= 1 && nextx <= n && nexty >= 1 && nexty <= m && mp[nextx][nexty] != 2 && !vis[nextx][nexty])
			{
				if(dis[nextx][nexty] > dis[now.x][now.y]+mp[nextx][nexty])
				{
					dis[nextx][nexty] = dis[now.x][now.y]+mp[nextx][nexty];
					q.push({nextx, nexty, dis[now.x][now.y]+mp[nextx][nexty]});
				}
			}
		}
	}
	if(flag)
		printf("%lld\n", ans*k);
	else
		puts("No Solution.");
	return 0;
}

        基于网格图上bfs的精简版:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <queue>
#define inf 0x3f3f3f3f
using namespace std;
//时间复杂度O(nlogn),最多遍历n个点,每个点最多加入4个点,即4nlogn 
//由于是网格图且边权具有特点,所以可以对纯dij堆优化做法进一步精简 
//可以当作优先队列bfs的模板 
int n, m, k, mp[5005][5005], _next[4][2] = {1, 0, 0, 1, -1, 0, 0, -1};
bool vis[5005][5005];
struct node
{
	int x, y, time;
};

struct cmp
{
	bool operator()(const node &a, const node &b)const
	{
		return a.time > b.time;
	}
};

signed main()
{
	scanf("%d%d%d", &n, &m, &k);
	for(int i = 1; i <= n; i++)
		for(int j = 1; j <= m; j++)
			scanf("%d", &mp[i][j]);
	if(mp[1][1] == 2 || mp[n][m] == 2)
	{
		puts("No Solution.");
		return 0;
	}
	priority_queue<node, vector<node>, cmp> q;
	long long ans;
	q.push({1, 1, mp[1][1]});
	bool flag = false;
	while(!q.empty())
	{
		node now = q.top();
		q.pop();
		if(now.x == n && now.y == m)
		{
			ans = now.time;
			flag = true;
			break;
		} 
		for(int i = 0; i <= 3; i++)
		{
			int nextx = now.x+_next[i][0], nexty = now.y+_next[i][1];
			if(nextx >= 1 && nextx <= n && nexty >= 1 && nexty <= m && mp[nextx][nexty] != 2 && !vis[nextx][nexty])
			{
				//由于到达同一点边权相同,且后加入集合的点dis更大,因此这里更新的dis就是最小的dis!! 
				vis[nextx][nexty] = true;
//				dis数组都没必要定义 
//				dis[nextx][nexty] = now.time+mp[nextx][nexty];
				q.push({nextx, nexty, now.time+mp[nextx][nexty]});
			}
		}
	}
	if(flag)
		printf("%lld\n", ans*k);
	else
		puts("No Solution.");
	return 0;
}

双端队列:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <deque>
#define inf 0x3f3f3f3f
using namespace std;
//如果需要立即遍历刚加入的点,可以选择双端队列 
int n, m, k, mp[5005][5005], _next[4][2] = {1, 0, 0, 1, -1, 0, 0, -1};
bool vis[5005][5005];
struct node
{
	int x, y, step;
};

signed main()
{
	scanf("%d%d%d", &n, &m, &k);
	for(int i = 1; i <= n; i++)
		for(int j = 1; j <= m; j++)
			scanf("%d", &mp[i][j]);
	if(mp[1][1] == 2 || mp[n][m] == 2)
	{
		puts("No Solution.");
		return 0;
	}
	deque<node> q;
	vis[1][1] = true;
	bool flag = false;
	long long ans;
	q.push_front({1, 1, mp[1][1]});
	while(!q.empty())
	{
		node now = q.front();
		q.pop_front();
		if(now.x == n && now.y == m)
		{
			flag = true;
			ans = now.step;
			break;
		}
		for(int i = 0; i <= 3; i++)
		{
			int nextx = now.x+_next[i][0], nexty = now.y+_next[i][1];
			if(nextx >= 1 && nextx <= n && nexty >= 1 && nexty <= m && !vis[nextx][nexty] && mp[nextx][nexty] != 2)
			{
				vis[nextx][nexty] = true;
				if(mp[nextx][nexty] == 0)
					q.push_front({nextx, nexty, now.step});//当前一步之内能到达的所有点 
				else
					q.push_back({nextx, nexty, now.step+1});//下一步才能到达的点 
			}
		}
	}
	if(flag)
		printf("%lld\n", ans*k);
	else
		puts("No Solution.");
	return 0;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值