Loj#6223 Luogu P4009 汽车加油行驶 分层图最短路

这是本蒟蒻博客的第一篇文章,不规范之处敬请各位大佬指正和谅解orz

Loj#6223+Luogu P4009


前言

本蒟蒻写博客发题解主要是为了加深对题目的理解,并且在枯燥的刷题过程中换换脑子理清思路,毕竟该蒟蒻博客很可能只有我一个人看(或者两个?),不管怎么说,一定会认真对待的。

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题,也可以用分层图最短路做。。。。

可以双倍经验一下。

作为本博客第一篇,蒟蒻感谢每一位来访者。

 霍格沃茨空中使用的魔法太多了,麻瓜使用的魔法的替代品——像蒸汽和电脑等在这里变得一塌糊涂。

                                                                                                   ——《霍格沃茨:一段校史》

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值