题目链接:OnlineJudge
这道题目是考察双端队列的,但是由于数据比较水,用优先队列也可以通过,我用两种方法来说一下吧!
先说一下用优先队列应该怎么做,如果图中每个位置的单元格中拥有相同的题目数,那么就是一个普通的bfs了,而由于每个位置的单元格中的题目数不尽相同,所以普通的bfs会出错,原因就在于我们无法确定用当前位置更新周围尚未更新的方格的方法是最优的,也就是说我们无法保证队列中的元素是单调的,因为单元格中题目数如果为0,那么相当于不花费时间,所以优先走题目数为0的格子得到的答案一定是最优解,也就是说我们可以采用优先队列,第一维存储到达当前格子所需的做题数,第二维存储当前格子的位置,也就是把对应的横纵坐标映射成一个数,然后我们正常的跑bfs就可以通过了,代码中有详解:
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
const int N=5020;
typedef pair<int,int> PII;
int t[N][N],d[N][N];
bool vis[N][N];
int n,m;
int x[4]={1,-1,0,0},y[4]={0,0,1,-1};
int bfs()
{
memset(d,0x3f,sizeof d);
d[1][1]=t[1][1];
priority_queue<PII,vector<PII>,greater<PII> > q;//维护一个优先队列
PII temp;
temp.first=d[1][1];temp.second=1;
q.push(temp);
while(q.size())
{
int TT=q.top().second;//每次用当前队列中所需做题数最小的格子更新是最优的
int tx=(TT-1)/m+1,ty=TT-(tx-1)*m;//将数分解成坐标形式
q.pop();
if(tx==n&&ty==m) break;
vis[tx][ty]=true;
for(int i=0;i<4;i++)
{
int nx=tx+x[i],ny=ty+y[i];
if(nx>=1&&ny>=1&&nx<=n&&ny<=m&&!vis[nx][ny])
{
vis[nx][ny]=true;//BFS中每个数只会进入一次
d[nx][ny]=d[tx][ty]+t[nx][ny];
PII temp;
temp.first=d[nx][ny];//数对的第一个数存储到达当前格子所需的做题数
temp.second=(nx-1)*m+ny;//将对应坐标映射成数
q.push(temp);
}
}
}
return d[n][m];
}
int main()
{
long long ppx;
cin>>n>>m>>ppx;
int px;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
scanf("%d",&px);
if(px==2) t[i][j]=0x3f3f3f3f,vis[i][j]=true;
else t[i][j]=px;
}
int ans=bfs();
if(ans==0x3f3f3f3f) printf("No Solution.");
else printf("%lld",ppx*ans);
return 0;
}
下面我来说一下双端队列的做法:
先说一下双端队列的一些前备知识:
头文件和队列一样都是#include<queue>
定义:deque<类型>q;(双端队列可以直接用q[i]访问元素)
基本操作:
把x从 队头 / 队尾 压入队列 :q.push_front(x) / q.push_back
访问 队头 / 队尾 元素 :q.front() / q.back()
删除 队头 / 队尾 元素 :q.pop_front() / q.pop_back()
判断队列是否为空:q.empty();
查找队列中元素数量:q.size();
清空队列:q.clear();
还有一点就是,双端队列支持sort(q.begin(),q.end()) 进行排序
对于这道题目而言双端队列和上面的优先队列的原理其实是差不多的,刚才说过了,因为单元格中题目数如果为0,那么相当于不花费时间,那么我们下次可以直接用本次更新的题目数为0的方格来更新,刚才是用优先队列来实现的,那我们如何用双端队列来实现呢?既然我们想优先使用题目数为0的单元格,我们就可以把他直接加到队列的队头,而对于题目数为1的方格我们可以加至队尾,这样就实现了优先使用题目数为0的方格。这样我们就保证了队列中元素的单调性。
双端队列相比于优先队列的优势在哪呢?就在于元素入队时,如果是双端队列的话就是o(1)的,而对于优先队列的话,入队是o(logn)的。(如果想进一步减少时间开销,直接可以数组模拟双端队列,也挺简单)
下面是代码:
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
const int N=5020;
typedef pair<int,int> PII;
int t[N][N],d[N][N];
bool vis[N][N];
int n,m;
int x[4]={1,-1,0,0},y[4]={0,0,1,-1};
int bfs()
{
memset(d,0x3f,sizeof d);
d[1][1]=t[1][1];
deque<PII>q;
PII temp;
temp.first=d[1][1];temp.second=1;
vis[1][1]=true;
q.push_back(temp);
while(q.size())
{
int TT=q.front().second;
int tx=(TT-1)/m+1,ty=TT-(tx-1)*m;//将数分解成坐标形式
q.pop_front();//优先使用题目数为0的格子,也就是队头的格子
if(tx==n&&ty==m) break;
for(int i=0;i<4;i++)
{
int nx=tx+x[i],ny=ty+y[i];
if(nx>=1&&ny>=1&&nx<=n&&ny<=m&&!vis[nx][ny])
{
vis[nx][ny]=true;//BFS中每个数只会进入一次
d[nx][ny]=d[tx][ty]+t[nx][ny];
PII temp;
temp.first=d[nx][ny];//数对的第一个数存储到达当前格子所需的做题数
temp.second=(nx-1)*m+ny;//将对应坐标映射成数
if(t[nx][ny]==0) q.push_front(temp);//题目数为0的格子放置队头
else q.push_back(temp);//题目数为1的格子放置队尾
}
}
}
return d[n][m];
}
int main()
{
long long ppx;
cin>>n>>m>>ppx;
int px;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
scanf("%d",&px);
if(px==2) t[i][j]=0x3f3f3f3f,vis[i][j]=true;
else t[i][j]=px;
}
int ans=bfs();
if(ans==0x3f3f3f3f) printf("No Solution.");
else printf("%lld",ppx*ans);
return 0;
}