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;
}