最小费用最大流——SPFA

模板题

首先什么是最大流
这里不详细赘述,需要的点这里

最小费用最大流概念
费用流中,网络上的每一条边都会比普通网络流中的边多一个属性——单位费用(这里边i的费用记为co[i])。也就是说,一旦流经边i的流量增加k,那么这条边的费用就会增加co[i]*k。开始时每条边的费用为0。
在一个网络中,一个流的总费用定义为其流经的每条边的费用之和。
根据我的理解,最小费用最大流(以下简称“最最”)其实是“最大流最小费用”。也就是说,最最问题要求你在网络中找一个流,这个流有以下要求:
①这个流是最大流,也就是说再也找不到一条增广路
②这个流是所有最大流中所需总花费最小的

最最问题的求解
最最问题有两种不同的思路:
I 先找出这个网络的最大流,然后再在这个网络上进行调整,使得流量保持不变,总费用减少,知道调整不过来为止。

II 每次在残余网络上找一条增广路,使得这条路上每个边的单位费用的和尽量小。然后不停地找这样的最短路,直到从源点到汇点走不通为止,这样增广出来的流便是最小费用最大流。

SPFA求解最大流的详细过程
SPFA求解最最问题采用第二种思路,具体步骤如下:
①一开始的流量为0,残余网络为原图
②以单位费用作为边的长度,以源点S为出发点,跑一遍SPFA,求出S到汇点T的最短路。若找不到,则当前流为最小费用最大流
③以这条最短路为增广路,进行增广,记录下当前的最大流和最小费用,然后返回②

其实也就是把Dinic的BFS改为SPFA,不要建分层图

模板题代码

#pragma GCC optimize(3)
#include
using namespace std;
typedef long long ak;
#define im INT_MAX
#define F(i,j,k) for(int i=j;i<=k;i++)
#define G(i,j,k) for(int i=j;i>=k;i--)
vectorway[5555],f[5555],fx[5555],co[5555];
//邻接表存图
//way[i][j]表示从i引出的第j条边到达的节点
//f[i][j]表示从i引出的第j条边可承受的最大流量
//fx[i][j]表示从j引出的指向i的边是所有起点为u的边中的第几条
//co[i][j]表示从i引出的第j条边的单位费用
ak n,m,s,t,ans1,ans2,pre[5555],pp[5555],dis[5555];
//ans1表示最大流,ans2表示最小费用
//pre[i]表示在当前增广路中,节点i的前驱,pre[s]==0
//pp[i]表示在当前增广路中,pre[i]到i的边是所有从pre[i]出发的边中的第几条
bool vis[5555];
//vis表示节点i在不在q中
queueq;
//q是SPFA所用的队列
bool spfa(){//SPFA,不会的同学找篇博客看看
	F(i,1,n)dis[i]=im,vis[i]=0,pre[i]=0;
	while(q.size())q.pop();
	q.push(s);dis[s]=0;vis[s]=1;
	while(q.size()){
		ak u=q.front();q.pop();vis[u]=0;
		F(i,0,way[u].size()-1){
			if(!way[u].size())break;
			ak v=way[u][i],c=co[u][i];
			if(f[u][i]&&(dis[v]>dis[u]+c)){//松弛操作必须保证从u到v的流量是正的
				dis[v]=dis[u]+c,pre[v]=u,pp[v]=i;
				if(!vis[v])q.push(v),vis[v]=1;
			}
		}
	}
	return dis[t]^im;//走得通返回1,否则返回0
}
void dfs(){//增广
	ak zg=im,cur=t;//zg表示当前增广路可以增广的流量 cur表示当前增广到哪一节点
	while(pre[cur]){//当cur不是源点
		int v=cur,u=cur=pre[cur];//cur变为cur的前驱
		zg=min(zg,f[u][pp[v]]);//求出每条边剩余流量的最小值,作为可以增广的流量
	}
	cur=t;
	while(pre[cur]){
		int v=cur,u=cur=pre[cur],po=pp[v];
		f[u][po]-=zg,f[v][fx[u][po]]+=zg;//反边加流量 正边减流量
		ans2+=co[u][po]*zg;//最小费用为所有边的费用之和 每条边的费用为这条边的单位费用乘以新增广的流量
	}
	ans1+=zg;//更新最大流
}
int main(){
	scanf("%lld%lld%lld%lld",&n,&m,&s,&t);
	F(i,1,m){
		ak u,v,w,x;
		scanf("%lld%lld%lld%lld",&u,&v,&w,&x);
		way[u].push_back(v);way[v].push_back(u);
		f[u].push_back(w);f[v].push_back(0);//反边初始流量为0
		fx[u].push_back(f[v].size()-1);
		fx[v].push_back(f[u].size()-1);
		co[u].push_back(x);co[v].push_back(-x);//反边费用为正边的相反数
	}
	while(spfa())dfs();//走得通就增广
	printf("%lld %lld\n",ans1,ans2);
	return 0;
}

你可能感兴趣的:(图论)