问题分析
- 首先应该弄清楚什么是最大网络流。
- 在生产生活中,除了要考虑最大流之外,还会考虑实现最大流的代价(耗费),因为不同的规格(容量…)的管道的成本是不同的。
- 现在我们想象假如我们有一个流量网络,现在每个边除了流量,现在还有一个单位费用,这条边的费用相当于它的单位费用乘上它的流量,我们要保持最大流的同时,还要保持边权最小,这就是最小费用最大流问题。因为在一个网络流图中,最大流量只有一个,但是“流法”有很多种,每种不同的流法所经过的边不同因此费用也就不同。
思路分析
- 在解决最大流问题时,我们通过不断寻找增广路来扩充增广量,从而让总流量达到最大。显然,对于同一个网络,最大流只有唯一一个值,但是流法会有很多种。这就意味着增广路的确定顺序并不会影响最大流,那么我们可以贪心地按照代价从小到大的顺序来寻找增广路。
- 对于每条增广路,
c
o
s
t
=
L
s
−
>
t
∗
f
l
o
w
L
cost = L_{s->t} \ * \ flow_L
cost=Ls−>t ∗ flowL。
我不会证明,但是 最小代价增广路 <=> 最小单位代价增广路 - 对于一条边,只有残余容量flow>0才认为可以走;对于一条边,正向走代价cost>0,则反向走代价理应为负数(正向边的相反数)。
- 在寻找最短路径的过程中,应记录下最短路径和这条路径的真实流量。
- 由于存在负权边,所以无法直接利用Dijkstra算法来找最短路径,此处我们使用可以处理负权边的SPFA算法。
- 注意手写队列时应实现为循环队列,以免溢出。
- 注意链式前向星的索引idx应从2开始,这样可以方便地访问到正向边对应的反向边。因为正向边和对应的反向边总是挨着的,利用xor(^)可以方便转换。
实现代码(EK+SPFA)
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N=5e3+10,M=5e4+10,INF=0x3f3f3f3f;
int n,m,s,t;
int idx=2,first[N],nxt[M*2],to[M*2],flow[M*2],cost[M*2];
int q[N],pre[N],dist[N],in[N];
bool inq[N];
int front=0,back=0,maxflow=0,mincost=0;
inline void addE(int u,int v,int f,int c)
{
to[idx]=v,flow[idx]=f,cost[idx]=c;
nxt[idx]=first[u],first[u]=idx++;
}
bool spfa()
{
memset(pre,0,sizeof(pre));
memset(dist,INF,sizeof(dist));
back=front=0;
q[(back++)%N]=s,inq[s]=true,dist[s]=0,in[s]=INF;
while(back!=front)
{
int u=q[(front++)%N];inq[u]=false;
for(int p=first[u];p;p=nxt[p])
{
int v=to[p],f=flow[p],c=cost[p];
if(f && dist[v]>dist[u]+c)
{
dist[v]=dist[u]+c;
in[v]=min(in[u],f);
pre[v]=p;
if(!inq[v])
q[(back++)%N]=v,inq[v]=true;
}
}
}
return dist[t]!=INF;
}
void update()
{
int x=t,f=in[t];
while(x!=s)
{
int p=pre[x];
flow[p]-=f;
flow[p^1]+=f;
x=to[p^1];
}
maxflow+=f,mincost+=f*dist[t];
}
int main()
{
scanf("%d%d%d%d",&n,&m,&s,&t);
for(int i=0;i<m;i++)
{
int u,v,f,c;
scanf("%d%d%d%d",&u,&v,&f,&c);
addE(u,v,f,c);
addE(v,u,0,-c);
}
while(spfa())
update();
printf("%d %d\n",maxflow,mincost);
return 0;
}
参考资料
- https://12349.blog.luogu.org/solution-p3381 (代码合我习惯)