这是本蒟蒻博客的第一篇文章,不规范之处敬请各位大佬指正和谅解orz
前言
本蒟蒻写博客发题解主要是为了加深对题目的理解,并且在枯燥的刷题过程中换换脑子理清思路,毕竟该蒟蒻博客很可能只有我一个人看(或者两个?),不管怎么说,一定会认真对待的。
Pass:声明:本博客参考:
P4009 汽车加油行驶问题(分层图最短路)_Mr.Gzj的博客-CSDN博客
一、建模
这道题其实代码和实现难度都很低,关键在于如何建模。
显然看到网络流24题基本上各方神犇都去跑最小费用最大流了,但蒟蒻这里提供另外一个方法——
分层图最短路
既然是求最小边权,我们容易想到是以费用为边权建图。
那么问题来了,如何建图呢?
网格图,那么最基本的就是根据题意描述,向四个放向连边。
但这道题真正难在对汽油的处理。
我们发现,汽油这一状态较为复杂.。
这里就需要引入分层图了。
把图复制若干张,对于每张图表示不同的状态,根据题意对不同的图层进行连边,这就是分层图。
题目中油箱容量为K
——实际上根据题目描述是满油可以走K条网格边,
这里曲解为油箱容量为K,每走一条边消耗一单位的油,方便理解——
所以我们需要建K+1张图,表示油箱含有0-K个单位的油
(也可以表示用了0-K单位的油,实现时微调即可)
如果要加满油,就向第K层(即油量为K的层)连边,
对于每一步,向下一层(假设是当前第Lay层,则下一层为Lay-1层(下同))连边
二、代码实现
1.节点在图中的编号(分层图的存储)
我们不妨把编号理解为一个点在图中的次序。
对于每一张网格图,行编号小的次序靠前,在同一行,列编号小的次序靠前。
那么对于K+1张图,依次排列即可。
节点的编号边是在它前面(次序比它小的)的节点的个数+1。
假设点p在第k张图的(i,j),那么它前面的节点数有:
前面图层的节点数n*n*k(存在第0层所以k不用减一)
前面行的节点数n*(i-1)
所在行前面的节点数(j-1)
加起来再加一即可
求编号代码如下:
int Get_Node(int x,int y,int z)//第z层的(x,y)的编号
{
return (n*n*z)+(n*(x-1))+y; //下层节点数+该行前节点数+列编号
}
2.建边
我们需要对油库和非油库分开考虑
对于非油库
汽车经过一条网格边时,若其 X坐标或Y坐标减小,则应付费用B,否则免付费用
那么建边方式就很显然了,向下一层的左和上方节点连一条权值为B的边。
向下一层的右和下方节点连一条权值为0的边。
在需要时可在网格点处增设油库,并付增设油库费用 C(不含加油费用A)
这也很显然,我们向第K层连一条权值为A+C的边。
一个小心思,读题不认真可能漏掉,不含加油费说明建厂的话还得交加油钱,要再加一个A。
对于油库
汽车在行驶过程中遇油库则应加满油并付加油费用 A
注意文字游戏,遇..则应,意味着强制消费,也无形埋下了一个坑。
既然是强制消费,那我们就有不同的建边方法。
首先肯定是向第K层建一条权值为A的边。
但既然是强制消费,那我们是不是就可以理解为不加满不给走?
那么不就是加满了才能走吗(废话文学)
所以我们不妨只由第K层向第K-1层的四个方向建相应的边,就可以实现只有满油才能离开了!
加边代码如下:
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
int Hasoil=Read();
if(Hasoil)//油库
{
for(int Lay=0;Lay<k;Lay++)//加油(理论上不会出现加油之前满油的情况,Lay<k)
Addedge(Get_Node(i,j,Lay),Get_Node(i,j,k),a);
if(i<n)
Addedge(Get_Node(i,j,k),Get_Node(i+1,j,k-1),0);
if(j<n)
Addedge(Get_Node(i,j,k),Get_Node(i,j+1,k-1),0);//往前跑免费
if(i>1)
Addedge(Get_Node(i,j,k),Get_Node(i-1,j,k-1),b);
if(j>1)
Addedge(Get_Node(i,j,k),Get_Node(i,j-1,k-1),b);//往回跑 交费
//强制消费只连满和满减一
}
else//非油库
{
for(int Lay=0;Lay<k;Lay++)//建厂+加油
Addedge(Get_Node(i,j,Lay),Get_Node(i,j,k),a+c);
for(int Lay=1;Lay<=k;Lay++)//注意第0层没油跑不动
{
if(i<n)
Addedge(Get_Node(i,j,Lay),Get_Node(i+1,j,Lay-1),0);
if(j<n)
Addedge(Get_Node(i,j,Lay),Get_Node(i,j+1,Lay-1),0);
if(i>1)
Addedge(Get_Node(i,j,Lay),Get_Node(i-1,j,Lay-1),b);
if(j>1)
Addedge(Get_Node(i,j,Lay),Get_Node(i,j-1,Lay-1),b);
}
}
}
3.求解
图建好了剩下就简单多了。
起点为左上角,终点为右下角,且:
出发时汽车已装满油,在起点与终点处不设油库。
那么我们以第K层的(1,1)为起点,对整个图跑一遍SPFA/Dijstra,
(没负权网格其实用堆优化的Dijstra更好, 但蒟蒻SPFA写得熟,但数据不卡无所谓了)
最后对于每层图的(n,n),求最小值,就可以华丽地输出了!
三、AC代码
Words are useless,I need your code....
#include<bits/stdc++.h>
const int N=1e6+100;
const int INF=0x3f3f3f3f;
//数组其实开大了, 点开到1e5,边开到1e6就好了
using namespace std;
int n,k,a,b,c;
inline int Read()
{
int x=0,f=1;
char ch=getchar();
while(!isdigit(ch))
{
if(ch=='-')
f=-1;
ch=getchar();
}
while(isdigit(ch))
{
x=(x<<1)+(x<<3)+ch-'0';
ch=getchar();
}
return x*f;
}
int Head[N],To[N<<1],Nxt[N<<1],W[N<<1],Etot;
void Addedge(int x,int y,int z)//加边
{
Nxt[++Etot]=Head[x];
W[Etot]=z;
To[Etot]=y;
Head[x]=Etot;
}
int Get_Node(int x,int y,int z)//第z层的(x,y)的编号
{
return (n*n*z)+(n*(x-1))+y; //下层节点数+该行前节点数+列编号
}
void Init()
{
n=Read(),k=Read(),a=Read(),b=Read(),c=Read();
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
int Hasoil=Read();
if(Hasoil)//油库
{
for(int Lay=0;Lay<k;Lay++)//加油(理论上不会出现加油之前满油的情况)
Addedge(Get_Node(i,j,Lay),Get_Node(i,j,k),a);
if(i<n)
Addedge(Get_Node(i,j,k),Get_Node(i+1,j,k-1),0);
if(j<n)
Addedge(Get_Node(i,j,k),Get_Node(i,j+1,k-1),0);//往前跑免费
if(i>1)
Addedge(Get_Node(i,j,k),Get_Node(i-1,j,k-1),b);
if(j>1)
Addedge(Get_Node(i,j,k),Get_Node(i,j-1,k-1),b);//往回跑 交费
//强制消费只连满和满减一
}
else//非油库
{
for(int Lay=0;Lay<k;Lay++)//建厂+加油
Addedge(Get_Node(i,j,Lay),Get_Node(i,j,k),a+c);
for(int Lay=1;Lay<=k;Lay++)//注意第0层没油跑不动
{
if(i<n)
Addedge(Get_Node(i,j,Lay),Get_Node(i+1,j,Lay-1),0);
if(j<n)
Addedge(Get_Node(i,j,Lay),Get_Node(i,j+1,Lay-1),0);
if(i>1)
Addedge(Get_Node(i,j,Lay),Get_Node(i-1,j,Lay-1),b);
if(j>1)
Addedge(Get_Node(i,j,Lay),Get_Node(i,j-1,Lay-1),b);
}
}
}
}
queue<int>Q;
int Dis[N];
bool Inque[N];
void SPFA(int st)
{
memset(Dis,0x3f,sizeof(Dis));
Q.push(st);
Inque[st]=true;
Dis[st]=0;
while(!Q.empty())
{
int u=Q.front();
Q.pop();
Inque[u]=false;
for(int i=Head[u];i;i=Nxt[i])
{
int v=To[i];
if(Dis[u]+W[i]<Dis[v])
{
Dis[v]=Dis[u]+W[i];
if(!Inque[v])
{
Q.push(v);
Inque[v]=true;
}
}
}
}
}
int main()
{
Init();
SPFA(Get_Node(1,1,k));
int ans=INF;
for(int i=0;i<=k;i++)
ans=min(Dis[Get_Node(n,n,i)],ans);
printf("%d\n",ans);
return 0;
}
//完结撒花 !!!
算法期望时间复杂度O(k*N*N*K)
总结
这道题其实算是分层图最短路的一道比较好的模板,其实用最短路来理解的话会比网络流想起来容易点?(主要是蒟蒻网络流太逊)
其实这道题和拯救大兵瑞恩(孤岛营救问题Loj6121+LuoguP4011)异曲同工也是网络流24题,也可以用分层图最短路做。。。。
可以双倍经验一下。
作为本博客第一篇,蒟蒻感谢每一位来访者。
霍格沃茨空中使用的魔法太多了,麻瓜使用的魔法的替代品——像蒸汽和电脑等在这里变得一塌糊涂。
——《霍格沃茨:一段校史》