最小费用最大流原理讲解(spfa模板向和板子)

最大流板子

最小费用最大流

如 果 你 会 最 大 流 , 那 这 个 基 本 上 可 以 秒 懂 。 如果你会最大流,那这个基本上可以秒懂。 ,

问 题 描 述 \color{Red}问题描述

给 出 一 个 网 络 图 , 以 及 其 源 点 s 和 汇 点 t 给出一个网络图,以及其源点s和汇点t st
每 条 边 已 知 其 最 大 流 量 和 单 位 流 量 费 用 每条边已知其最大流量和单位流量费用
求 出 其 网 络 最 大 流 和 在 最 大 流 情 况 下 的 最 小 费 用 。 求出其网络最大流和在最大流情况下的最小费用。

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

可以发现相比最大流多了一个单位流量费用的限制
那么原来利用bfs找路径的思想就不可行了,因为找的路径代价可能很大

那 找 怎 样 的 路 径 比 较 合 理 ? \color{Orange}那找怎样的路径比较合理? ?

把 单 位 费 用 看 作 边 的 权 值 , 从 源 点 到 汇 点 跑 最 短 路 把单位费用看作边的权值,从源点到汇点跑最短路 ,

每 次 找 一 条 s 到 t 的 花 费 最 小 的 路 径 , 同 时 记 录 路 径 和 路 上 的 最 小 流 量 每次找一条s到t的花费最小的路径,同时记录路径和路上的最小流量 st,

如 果 汇 点 的 最 短 路 被 更 新 , 说 明 找 到 路 径 , 完 成 了 b f s 判 断 是 否 有 路 径 的 功 能 如果汇点的最短路被更新,说明找到路径,完成了bfs判断是否有路径的功能 ,,bfs

而且 通 过 记 录 走 到 汇 点 的 路 径 , 可 以 直 接 对 路 径 上 边 的 流 量 做 修 改 通过记录走到汇点的路径,可以直接对路径上边的流量做修改 ,

如 此 一 来 , 不 停 的 利 用 s p f a 找 最 短 路 即 可 如此一来,不停的利用spfa找最短路即可 ,spfa

最 小 费 用 最 大 流 ( s p f a 版 本 ) 就 是 这 样 一 种 算 法 最小费用最大流(spfa版本)就是这样一种算法 (spfa)

#include 
using namespace std;
const int maxn=50009;
const int inf=1e9;
int n,m,s,t;
int maxflow,mincost;
int dis[maxn],head[maxn<<1],cnt=1,incf[maxn],pre[maxn],vis[maxn];
struct EDGE{
	int to,nxt,w,flow;//分别代表 
}d[maxn<<1];
void add(int u,int v,int flow,int w)//flow最大流量,w单位费用
{
	d[++cnt].to=v,d[cnt].flow=flow,d[cnt].w=w,d[cnt].nxt=head[u],head[u]=cnt;	
} 
bool spfa()
{
	queueq;
	for(int i=0;i<=n;i++)	dis[i]=inf;
	memset(vis,0,sizeof(vis));
	q.push(s);
	dis[s]=0,vis[s]=1;
	incf[s] = inf;//初始流量无限大
	while( !q.empty() )
	{
		int u=q.front(); q.pop();
		vis[u]=0;//出队
		for(int i=head[u];i;i=d[i].nxt)
		{
			if( !d[i].flow )	continue;//无流量了	
			int v=d[i].to;
			if( dis[v]>dis[u]+d[i].w )
			{
				dis[v]=dis[u]+d[i].w;
				incf[v] = min(incf[u],d[i].flow);//更新当前流量
				pre[v]=i;//记录从哪条边过来的
				if( !vis[v] )	vis[v]=1,q.push(v); 
			}
		}	
	} 
	if( dis[t]==inf )	return 0;
	return 1;
}
void dinic()
{
	while( spfa() )
	{
		int x=t;//倒回去找路径
		maxflow+=incf[t];
		mincost+=dis[t]*incf[t];
		int i;
		while(x != s)
		{
			i=pre[x];
			d[i].flow-=incf[t];//减去流量
			d[i^1].flow+=incf[t];//加上流量
			x = d[i^1].to;//因为是倒回去,所以利用反向边倒回去 
		 } 
	}
}
int main()
{
	cin >> n >> m >> s >> t;
	for(int i=1;i<=m;i++)
	{
		int l,r,w,x;
		cin >> l >> r >> w >> x;
		add(l,r,w,x); add(r,l,0,-x);
	}
	dinic();
	cout << maxflow << " " << mincost;
}

下面是最大费用最大流,就是建边的时候把边权取反就行,输出再次取反

#include 
using namespace std;
const int maxn=50009;
const int inf=1e9;
int n,m,s,t,a[59][59];
int maxflow,mincost;
int dis[maxn],head[maxn<<1],cnt=1,incf[maxn],pre[maxn],vis[maxn];
struct EDGE{
     
	int to,nxt,w,flow;//分别代表 
}d[maxn<<1];
void add(int u,int v,int flow,int w)//flow最大流量,w单位费用
{
     
	d[++cnt].to=v,d[cnt].flow=flow,d[cnt].w=w,d[cnt].nxt=head[u],head[u]=cnt;
	d[++cnt].to=u,d[cnt].flow=0,d[cnt].w=-w,d[cnt].nxt=head[v],head[v]=cnt;	
} 
bool spfa()
{
     
	queue<int>q;
	for(int i=0;i<=t;i++)	dis[i]=inf;
	memset(vis,0,sizeof(vis));
	q.push(s);
	dis[s]=0,vis[s]=1;
	incf[s] = inf;//初始流量无限大
	while( !q.empty() )
	{
     
		int u=q.front(); q.pop();
		vis[u]=0;//出队
		for(int i=head[u];i;i=d[i].nxt)
		{
     
			if( !d[i].flow )	continue;//无流量了	
			int v=d[i].to;
			if( dis[v]>dis[u]+d[i].w )
			{
     
				dis[v]=dis[u]+d[i].w;
				incf[v] = min(incf[u],d[i].flow);//更新当前流量
				pre[v]=i;//记录从哪条边过来的
				if( !vis[v] )	vis[v]=1,q.push(v); 
			}
		}	
	} 
	if( dis[t]==inf )	return 0;
	return 1;
}
void dinic()
{
     
	while( spfa() )
	{
     
		int x=t;//倒回去找路径
		maxflow+=incf[t];
		mincost+=dis[t]*incf[t];
		int i;
		while(x != s)
		{
     
			i=pre[x];
			d[i].flow-=incf[t];//减去流量
			d[i^1].flow+=incf[t];//加上流量
			x = d[i^1].to;//因为是倒回去,所以利用反向边倒回去 
		 } 
	}
}
int main()
{
     
	cin >> n >> m;
	s=0,t=n*n*2+1;
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)	cin >> a[i][j];
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)
	{
     
		int x=(i-1)*n+j,y=i*n+j;
		if( i!=n )	add(x+n*n,y,inf,0);
		if( j!=n )	add(x+n*n,x+1,inf,0);
		add(x,x+n*n,1,-a[i][j]);
		add(x,x+n*n,inf,0);
	}
	add(s,1,m,0);
	add(n*n*2,t,m,0);
	dinic();
	cout << -mincost;
}

你可能感兴趣的:(我的模板类)