题目链接:点击查看
题目大意:给出一个n*n的矩阵表示道路,途中有一些加油站,现在要从点(1,1)到达点(n,n),问最小花费,其中的一些规则如下:
- 汽车只能沿着网格边行驶,装满油后可以行驶K条边,出发时已经装满油
- 汽车经过一条网格边时,若x或y减小,需要花费B元,其余情况没有花费
- 汽车在行驶过程中遇到油库则必须强制加满油,并花费A元
- 在需要时可以在网格点增设临时油库,并支付花费C元(不包括加油的A元)
- 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,k),流量为1,花费为0
- (x,y,k)->(xx,yy,k-1),流量为无穷大,花费符合条件2
- 当前点是否为油库:
- 是油库:(x,y,t)t∈[0,k-1]->(x,y,k),流量为无穷大,花费为a
- 不是油库:(x,y,0)->(x,y,k),流量为无穷大,花费为a+c
- (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;
}