重修 MCMF 最小费用最大流


用途

在满足最大流的情况下最小的费用

算法思路

每次找到一条费用之和最小的增广路径增广(反向边的费用是正向边的相反数)。

易证明贪心正确,或参考 OI Wiki 的证明。

具体实现

每次在残量网络中跑 Bellman-Ford 或 SPFA按费用跑最短路),取路径上流量最小值增广,修改答案并改动网络上的这条路径。

注意我们不像 Dinic 一样按树形增广,而是像 EK 一样单条路径树形也行,不过时间一样,何必呢。

时间复杂度

\(O(nmf)\) 点数、边数、最大流的积。

最坏可以到达 \(O(n^32^{n/2})\),其实不刻意卡的话是伪多项式复杂度的。

由于 SPFA 已经玄学了,MCMF 时间复杂度更是玄学。

例题 & Code

P3381 【模板】最小费用最大流

#include
using namespace std;
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define For(i,j,k) for(int i=j;i<=k;i++)
#define Rof(i,j,k) for(int i=j;i>=k;i--)
#define N 5010
#define M 50010
#define inf 0x7fffffff
struct edge{int to,flow,cost,nxt;}e[2*M];//2 times of edges!!!
int head[N],tot=1;
int n,m,S,T;
inline void adde(int x,int y,int flow,int cost){
	e[++tot]=(edge){y,flow, cost,head[x]}; head[x]=tot;
	e[++tot]=(edge){x,0   ,-cost,head[y]}; head[y]=tot;
}
int dis[N];//min toll to i (which SPFA works)
int flow[N];//the flow of the path with the min toll from S to i (just ONE path)
int pre[N],lst[N];//the last node and edge of the path with the min toll
queue q;//for SPFA
bool ins[N];//in the queue or not to reduce the useless 'q.push()'
bool spfa(){
	while(!q.empty()) q.pop();
	For(i,1,n) ins[i]=pre[i]=0;
	For(i,1,n) dis[i]=inf;
	dis[S]=0;
	ins[S]=1;
	flow[S]=inf;
	q.push(S);
	int x;
	while(!q.empty()){
		x=q.front();
		q.pop();
		ins[x]=0;
		for(int i=head[x];i;i=e[i].nxt){
			int to=e[i].to;
			assert(e[i].flow>=0);
			if(e[i].flow==0 || dis[to]<=dis[x]+e[i].cost) continue;
			dis[to]=dis[x]+e[i].cost;
			pre[to]=x;
			lst[to]=i;
			flow[to]=min(flow[x],e[i].flow);
			if(!ins[to]){
				ins[to]=1;
				q.push(to);
			}
		}
	}
	return dis[T]>n>>m>>S>>T;
	int x,y,w,c;
	while(m--){
		cin>>x>>y>>w>>c;
		adde(x,y,w,c);
	}
	mcmf();
return 0;}

去注释版:

#include
using namespace std;
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define For(i,j,k) for(int i=j;i<=k;i++)
#define Rof(i,j,k) for(int i=j;i>=k;i--)
#define N 5010
#define M 50010
#define inf 0x7fffffff
struct edge{int to,flow,cost,nxt;}e[2*M];
int head[N],tot=1;
int n,m,S,T;
inline void adde(int x,int y,int flow,int cost){
	e[++tot]=(edge){y,flow, cost,head[x]}; head[x]=tot;
	e[++tot]=(edge){x,0   ,-cost,head[y]}; head[y]=tot;
}
int dis[N];
int flow[N];
int pre[N],lst[N];
queue q;
bool ins[N];
bool spfa(){
	while(!q.empty()) q.pop();
	For(i,1,n) ins[i]=pre[i]=0;
	For(i,1,n) dis[i]=inf;
	dis[S]=0;
	ins[S]=1;
	flow[S]=inf;
	q.push(S);
	int x;
	while(!q.empty()){
		x=q.front();
		q.pop();
		ins[x]=0;
		for(int i=head[x];i;i=e[i].nxt){
			int to=e[i].to;
			assert(e[i].flow>=0);
			if(e[i].flow==0 || dis[to]<=dis[x]+e[i].cost) continue;
			dis[to]=dis[x]+e[i].cost;
			pre[to]=x;
			lst[to]=i;
			flow[to]=min(flow[x],e[i].flow);
			if(!ins[to]){
				ins[to]=1;
				q.push(to);
			}
		}
	}
	return dis[T]>n>>m>>S>>T;
	int x,y,w,c;
	while(m--){
		cin>>x>>y>>w>>c;
		adde(x,y,w,c);
	}
	mcmf();
return 0;}

AcWing382. K取方格数

算是 传纸条 的加强版吧。

#include
using namespace std;
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define fir first
#define sec second
#define mkp make_pair
#define pb emplace_back
#define mem(x,y) memset(x,y,sizeof(x))
#define For(i,j,k) for(int i=j;i<=k;i++)
#define Rof(i,j,k) for(int i=j;i>=k;i--)
#define ckmx(a,b) a=max(a,b)
#define ckmn(a,b) a=min(a,b)
#define fin(s) freopen(s,"r",stdin)
#define fout(s) freopen(s,"w",stdout)
#define file(s) fin(s".in");fout(s".out")
#define cerr cerr<<'_'
#define debug cerr<<"Passed line #"<<__LINE__<T ov(T x){cerr<<"Value: "< q;//for SPFA
bool ins[2*N*N];//in the queue or not to reduce the useless 'q.push()'
bool spfa(){
	while(!q.empty()) q.pop();
	For(i,1,lim) ins[i]=pre[i]=0;
	For(i,1,lim) dis[i]=inf;
	dis[S]=0;
	ins[S]=1;
	flow[S]=inf;
	q.push(S);
	int x;
	while(!q.empty()){
		x=q.front();
		q.pop();
		ins[x]=0;
		for(int i=head[x];i;i=e[i].nxt){
			int to=e[i].to;
			assert(e[i].flow>=0);
			if(e[i].flow==0 || dis[to]<=dis[x]+e[i].cost) continue;
			dis[to]=dis[x]+e[i].cost;
			pre[to]=x;
			lst[to]=i;
			flow[to]=min(flow[x],e[i].flow);
			if(!ins[to]){
				ins[to]=1;
				q.push(to);
			}
		}
	}
	return dis[T]>n>>k;
	int x;
	For(i,1,n) For(j,1,n){
		cin>>x;
		adde(num(i,j,0),num(i,j,1),1,-x);
		adde(num(i,j,0),num(i,j,1),k,0);//'k' means inf 
	}
	For(i,1,n) For(j,1,n-1) adde(num(i,j,1),num(i,j+1,0),k,0);
	For(i,1,n-1) For(j,1,n) adde(num(i,j,1),num(i+1,j,0),k,0);
	S=1,lim=T=num(n,n,1)+1;
	adde(S,num(1,1,0),k,0);//but only here not,JUST k 
	adde(num(n,n,1),T,k,0);
	cout<<(-mcmf())<
OI