洛谷 - P4009 汽车加油行驶问题(分层图最短路/最小费用最大流)

57 篇文章 1 订阅
24 篇文章 0 订阅

题目链接:点击查看

题目大意:给出一个n*n的矩阵表示道路,途中有一些加油站,现在要从点(1,1)到达点(n,n),问最小花费,其中的一些规则如下:

  1. 汽车只能沿着网格边行驶,装满油后可以行驶K条边,出发时已经装满油
  2. 汽车经过一条网格边时,若x或y减小,需要花费B元,其余情况没有花费
  3. 汽车在行驶过程中遇到油库则必须强制加满油,并花费A元
  4. 在需要时可以在网格点增设临时油库,并支付花费C元(不包括加油的A元)
  5. N,K,A,B,C均为正整数,且满足2<=N<=100,2<=K<=10

输出从起点到达终点的最小费用

题目分析:又是条件冗杂的一道题目,不过是一个比较明显的分层图问题,又是要求最小花费,我们可以直接涉及一个动态规划的转移方程,然后用spfa迭代更新就好了,这个一会直接挂个代码就行了,因为最短路写起来比较简单也比较好理解

然后重点说一下怎么用费用流求解吧,因为这个题目是要求最小花费,所以建好图后直接跑费用流也是可行的,关于建图,我们也可以直接建分层图,首先抛去油箱与加油站的情况,如果只是要求从起点到终点的最短路,那么我们直接在一层中进行连边,源点连向起点,流量为1,花费为0,然后按照上面的条件2连边,最后让终点连向汇点就好了,至于多出来的油箱限制,我们可以将每一种情况视为新的一层,我选择的是将k拆为k层图,每一层图代表当前油箱剩余的油还有多少,那么在转移的时候,符合条件的连边就可以直接从(x,y,k)连边到(xx,yy,k-1)了,对于每个油库,因为是强制满油,所以我们可以将该点[0,k-1]层的点都向第k层的点连边,花费为a,只有第k层才能向四周连边,而对于非油库的点,最优解肯定是当油箱空了的时候才建立临时油库,所以只需要让第0层的该点向第k层建边,花费为a+c就好了,具体细节还真就没有了,主要还是在代码实现的过程中一定要细心细心再细心,一开始建边的时候因为小于等于漏了一个等于号,调了20多分钟。。太菜了:

三维点表示的是(x,y,k),x,y代表坐标,k代表层数

  1. 源点->(1,1,k),流量为1,花费为0
  2. (x,y,k)->(xx,yy,k-1),流量为无穷大,花费符合条件2
  3. 当前点是否为油库:
    1. 是油库:(x,y,t)t∈[0,k-1]->(x,y,k),流量为无穷大,花费为a
    2. 不是油库:(x,y,0)->(x,y,k),流量为无穷大,花费为a+c
  4. (n,n,t)t∈[0,k]->汇点,流量为无穷大,花费为0

关于费用流的建图就到此为止了,按照上述要求建好图后直接跑最小费用最大流就是答案了

代码:

最小费用最大流:

#include<iostream>
#include<cstdlib>
#include<string>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<climits>
#include<cmath>
#include<cctype>
#include<stack>
#include<queue>
#include<list>
#include<vector>
#include<set>
#include<map>
#include<sstream>
using namespace std;
    
typedef long long LL;
    
const int inf=0x3f3f3f3f;
 
const int N=2e5+100;//点
 
const int M=1e6+100;//边

const int bb[4][2]={0,1,0,-1,1,0,-1,0};
 
struct Edge
{
	int to,w,cost,next;
}edge[M];
 
int head[N],cnt;
 
void addedge(int u,int v,int w,int cost)
{
	edge[cnt].to=v;
	edge[cnt].w=w;
	edge[cnt].cost=cost;
	edge[cnt].next=head[u];
	head[u]=cnt++;
	edge[cnt].to=u;
	edge[cnt].w=0;
	edge[cnt].cost=-cost;
	edge[cnt].next=head[v];
	head[v]=cnt++;
}
 
int d[N],incf[N],pre[N],n;
 
bool vis[N];
 
bool spfa(int s,int t)
{
	memset(d,inf,sizeof(d));
	memset(vis,false,sizeof(vis));
    memset(pre,-1,sizeof(pre));
	queue<int>q;
	q.push(s);
	vis[s]=true;
	incf[s]=inf;
	d[s]=0;
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		vis[u]=false;
		for(int i=head[u];i!=-1;i=edge[i].next)
		{
			int v=edge[i].to;
			int w=edge[i].w;
			int cost=edge[i].cost;
			if(!w)
				continue;
			if(d[v]>d[u]+cost)
			{
				d[v]=d[u]+cost;
				pre[v]=i;
				incf[v]=min(incf[u],w);
				if(!vis[v])
				{
					vis[v]=true;
					q.push(v);
				}
			}
		}
	}
	return pre[t]!=-1;
}
 
int update(int s,int t)
{
	int x=t;
	while(x!=s)
	{
		int i=pre[x];
		edge[i].w-=incf[t];
		edge[i^1].w+=incf[t];
		x=edge[i^1].to;
	}
	return d[t]*incf[t];
}
 
void init()
{
	memset(head,-1,sizeof(head));
	cnt=0;
}
 
int solve(int st,int ed)
{
	int ans=0;
	while(spfa(st,ed))
		ans+=update(st,ed);
	return ans;
}

int get_id(int x,int y,int k)//第k层的(x,y) 
{
	return (x-1)*n+y+k*n*n;
}

int main()
{
//  freopen("input.txt","r",stdin);
//  ios::sync_with_stdio(false);
	init();
	int k,a,b,c,st=N-1,ed=st-1;
	scanf("%d%d%d%d%d",&n,&k,&a,&b,&c);
	addedge(st,get_id(1,1,k),1,0);//源点->(1,1,k) 
	for(int i=0;i<=k;i++)//(n,n,kk)kk∈[0,k]->汇点 
		addedge(get_id(n,n,i),ed,inf,0);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
		{
			int val;
			scanf("%d",&val);
			if(val)//强制加油
			{
				for(int t=0;t<k;t++)//(i,j,kk)kk∈[0,k-1]->(i,j,k) 
					addedge(get_id(i,j,t),get_id(i,j,k),inf,a);
				for(int t=0;t<4;t++)//(i,j,k)->(xx,yy,k-1) 
				{
					int xx=i+bb[t][0];
					int yy=j+bb[t][1];
					if(xx<=0||yy<=0||xx>n||yy>n)
						continue;
					int len=0;
					if(xx<i||yy<j)
						len=b;
					addedge(get_id(i,j,k),get_id(xx,yy,k-1),inf,len);
				}
			} 
			else
			{
				for(int t=0;t<4;t++)
				{
					int xx=i+bb[t][0];
					int yy=j+bb[t][1];
					if(xx<=0||yy<=0||xx>n||yy>n)
						continue;
					int len=0;
					if(xx<i||yy<j)
						len=b;
					for(int kk=1;kk<=k;kk++)
						addedge(get_id(i,j,kk),get_id(xx,yy,kk-1),inf,len);
				}
				addedge(get_id(i,j,0),get_id(i,j,k),inf,a+c);
			}
		}
	printf("%d\n",solve(st,ed));
	
	
	
	
	
	
	
    
    
 
        
        
        
         
        
    return 0;
}

分层图最短路(spfa):

#include<iostream>
#include<cstdlib>
#include<string>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<climits>
#include<cmath>
#include<cctype>
#include<stack>
#include<queue>
#include<list>
#include<vector>
#include<set>
#include<map>
#include<sstream>
using namespace std;
    
typedef long long LL;
    
const int inf=0x3f3f3f3f;
 
const int N=110;

const int bb[4][2]={0,1,0,-1,1,0,-1,0};

int maze[N][N],d[N][N][15],n,K,a,b,c;

bool vis[N][N][15];

struct Node
{
	int x,y,k;
	Node(int X,int Y,int K)
	{
		x=X;
		y=Y;
		k=K;
	}
};

void spfa()
{
	memset(vis,false,sizeof(vis));
	memset(d,inf,sizeof(d));
	queue<Node>q;
	q.push(Node(1,1,K));
	vis[1][1][K]=true;
	d[1][1][K]=0;
	while(q.size())
	{
		Node cur=q.front();
		q.pop();
		int x=cur.x;
		int y=cur.y;
		int k=cur.k;
		vis[x][y][k]=false;
		if(k<K)//加油
		{
			if(maze[x][y])//有加油站
			{
				if(d[x][y][K]>d[x][y][k]+a)
				{
					d[x][y][K]=d[x][y][k]+a;
					if(!vis[x][y][K])
					{
						vis[x][y][K]=true;
						q.push(Node(x,y,K));
					}
				}
				continue;//强制加油
			}
			else//没加油站 
			{
				if(d[x][y][K]>d[x][y][k]+a+c)
				{
					d[x][y][K]=d[x][y][k]+a+c;
					if(!vis[x][y][K])
					{
						vis[x][y][K]=true;
						q.push(Node(x,y,K));
					}
				}
			} 
		} 
		if(k)//跑路
		{
			for(int i=0;i<4;i++)
			{
				int xx=x+bb[i][0];
				int yy=y+bb[i][1];
				if(xx<=0||yy<=0||xx>n||yy>n)
					continue;
				int len=0;
				if(xx<x||yy<y)
					len=b;
				if(d[xx][yy][k-1]>d[x][y][k]+len)
				{
					d[xx][yy][k-1]=d[x][y][k]+len;
					if(!vis[xx][yy][k-1])
					{
						vis[xx][yy][k-1]=true;
						q.push(Node(xx,yy,k-1));
					}
				}
			}
		} 
	}
}
 
int main()
{
//  freopen("input.txt","r",stdin);
//  ios::sync_with_stdio(false);
	scanf("%d%d%d%d%d",&n,&K,&a,&b,&c);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			scanf("%d",&maze[i][j]);
	spfa();
	int ans=inf;
	for(int i=0;i<=K;i++)
		ans=min(ans,d[n][n][i]);
	printf("%d\n",ans);
	
	
	
	
	
    
    
 
        
        
        
         
        
    return 0;
}

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
1.问题描述 给定一个N*N 的方形网格,设其左上角为起点,坐标为(1,1),X 轴向右为正,Y 轴 向下为正,每个方格边长为1。一辆汽车从起点出发驶向右下角终点,其坐标为(N,N)。 在若干个网格交叉点处,设置了油库,可供汽车行驶途中加油汽车行驶过程中应遵守 如下规则: (1)汽车只能沿网格边行驶,装满油后能行驶K 条网格边。出发时汽车已装满油,在 起点与终点处不设油库。 (2)当汽车行驶经过一条网格边时,若其X 坐标或Y 坐标减小,则应付费用B,否则 免付费用。 (3)汽车行驶过程中遇油库则应加满油并付加油费用A。 (4)在需要时可在网格点处增设油库,并付增设油库费用C(不含加油费用A)。 (5)(1)~(4)中的各数N、K、A、B、C均为正整数。 算法设计: 求汽车从起点出发到达终点的一条所付费用最少的行驶路线。 数据输入: 输入数据。第一行是N,K,A,B,C的值,2 <= N <= 100, 2 <= K <= 10。第二行起是一个N*N 的0-1方阵,每行N 个值,至N+1行结束。方阵的第i 行第j 列处的值为1 表示在网格交叉点(i,j)处设置了一个油库,为0 时表示未设油库。 各行相邻的2 个数以空格分隔。 结果输出: 将找到的最优行驶路线所需的费用,即最小费用输出. Sample input 9 3 2 3 6 0 0 0 0 1 0 0 0 0 0 0 0 1 0 1 1 0 0 1 0 1 0 0 0 0 1 0 0 0 0 0 0 1 0 0 1 1 0 0 1 0 0 1 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 1 1 0 0 1 0 0 0 1 0 0 1 0 0 0 0 0 0 0 Sample output 12

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Frozen_Guardian

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值