题解:P3381 【模板】最小费用最大流
upd on 2025.5.10:修正证明。感谢 shihaojiacaigitcl 指出的错误!
upd on 2025.6.9:优化。
思路
直接将 EK 算法中的 BFS 函数换成 SPFA 即可。
在残留网络中,如果正向边的边权为
证明
正确性证明:
设
假设可行流
-
路径:至少存在一条路径
p' ,使得p' 的长度小于p 的长度,否则f_n 的费用不可能小于f 的费用。 -
环:由于
G_f 中不存在负环,所以环贡献的长度和费用一定非负,为了降低费用,一定不选环。
因为
现在证明加黑部分:
设
因为
所以:
这就与
这时候有些聪明的同学就发现了:如果一条边已经流了一些流量后,那么反向边的边权就会变成负数,那么如何保证不会出现负环呢?
定义 d[u]。我们假设第
因为我们是按照最短路径增广的,所以
时间复杂度
最坏情况下,此算法的时间复杂度为
参考代码:
#include<bits/stdc++.h>
#define mems(a,b) memset(a,b,sizeof a)
using namespace std;
const int N=5010,M=100010,inf=0x3f3f3f3f;
int n,m,S,T;
int h[N],e[M],ne[M],f[M],w[M],idx;
int q[N],d[N],pre[N],incf[N];//d表示从源点到每个点的长度,incf表示走到每个点时的最大流量
bool st[N];
void add(int a,int b,int c,int d){
e[idx]=b;
f[idx]=c;
w[idx]=d;
ne[idx]=h[a];
h[a]=idx++;
e[idx]=a;
f[idx]=0;
w[idx]=-d;
ne[idx]=h[b];
h[b]=idx++;
}
bool spfa(){
int hh=0,tt=1;
mems(d,0x3f);
mems(incf,0);
q[0]=S;
d[S]=0;
incf[S]=inf;//源点的流量无限制
while(hh!=tt){
int t=q[hh++];//出队
if(hh==N) hh=0;//循环队列
st[t]=false;//一个点可以多次入队
for(int i=h[t];~i;i=ne[i]){
int ver=e[i];
if(f[i]>0 && d[ver]>d[t]+w[i]){
d[ver]=d[t]+w[i];//求最短路
pre[ver]=i;//到ver的边是i这条边
incf[ver]=min(f[i],incf[t]);
if(!st[ver]){
q[tt++]=ver;//入队
if(tt==N) tt=0;
st[ver]=true;
}
}
}
}
return (incf[T]>0);//判断汇点的流量是否大于0,等价于判断能不能到汇点
}
void EK(int &flow,int &cost){//传引用
flow=cost=0;
while(spfa()){
int t=incf[T];//流量
flow+=t;
cost+=t*d[T];
for(int i=T;i!=S;i=e[pre[i]^1]){//反向边的终点就是正向边的起点
f[pre[i]]-=t;
f[pre[i]^1]+=t;
}
}
}
int main(){
cin>>n>>m>>S>>T;
mems(h,-1);
while(m--){
int a,b,c,d;
scanf("%d%d%d%d",&a,&b,&c,&d);
add(a,b,c,d);
}
int flow,cost;
EK(flow,cost);
cout<<flow<<" "<<cost;
return 0;
}