费用流基础

费用流

问题

给定一个包含 n n n 个点 m m m 条边的有向图,并给定每条边的容量和费用,边的容量非负。

图中可能存在重边和自环,保证费用不会存在负环。

求从 S S S T T T 的最大流,以及在流量最大时的最小费用。

分析

首先大胆假设,在残留网络上沿着最短路(边权即费用)增广,直到得到最大流(无法再增广),那么,假如图中没有负环,这样的最大流的费用是最小的。

证明

下面证明正确性:
假如存在具有相同流量 f f f 费用更小的流 f ’ f’ f ,考察 f ’ − f f’-f ff ,可知它由若干个圈组成,因为 f ’ − f f’-f ff 的费用为负,故在这些圈中至少存在一个负环

也就是说, f f f 是最小费用流 ⇔ \Leftrightarrow 残留网络中不存在负环

下面用归纳法论证:

  1. 对于流量为 0 0 0 的流 f 0 f_0 f0 ,残留网络即为原图,故只要不存在负环,那么 f 0 f_0 f0 就是最小费用流。
  2. 下证:若流量为 i i i 的流 f i f_i fi 为最小费用流,则其通过 s s s t t t 的最短路增广得到的 f i + 1 f_{i+1} fi+1 是流量为 i + 1 i+1 i+1 的最小费用流。

假设存在 f i + 1 ’ f_{i+1}’ fi+1 满足 f i + 1 ’ < f i + 1 f_{i+1}’ < f_{i+1} fi+1<fi+1 ,而 f i + 1 − f i f_{i+1}-f_i fi+1fi 对应的路径又是 s → t s\rightarrow t st 的最短路,所以 f i + 1 ’ − f i f_{i+1}’-f_i fi+1fi 对应的路径一定存在负环,与 f i f_i fi 为最小费用流矛盾。

那么,如果图中存在负环,如何解决呢?

此时我们需要对问题进行转化,从 t → s t\rightarrow s ts 连一条容量 ∞ ∞ 费用 − - 的虚边得到无源汇流

注意到如果存在增广路,则一定可以通过这条路径以及虚边使得费用更小(走完后再走一个 − ∞ -∞ ),反之,如果不存在增广路,则在原图相应地得到了最小费用最大流。

下面只需求无源汇流的最小费用。

我们采取 capacity scaling 解决这个问题:

它利用了流本身的性质,实现减少增广的次数的目的。

这个性质是:将原图中每条边的容量乘二后,最小费用流每条边的流量分别乘二。
因此,在求出 ⌊ c ( u , v ) 2 k ⌋ \lfloor \frac{c(u,v)}{2^k} \rfloor 2kc(u,v) 为边容量的最小费用流 f f f 后,将 f f f 对应的边容量乘二,然后对每条二进制中相应位为 1 1 1 的边进行容量加一即可得到 ⌊ c ( u , v ) 2 k − 1 ⌋ \lfloor \frac{c(u,v)}{2^{k-1}} \rfloor 2k1c(u,v) ,最后我们即可得到相应边 ( u , v ) (u,v) (u,v) 容量为 c ( u , v ) c(u,v) c(u,v) 对应的最小费用流。

的证

代码

#include <bits/stdc++.h>
using namespace std;
const int N = 5005;
const int M = 100005; 
const int INF = 1e8;

int n,m,c,w,u,v,S,T;

int head[N], tot = 0;
struct node{
	int to, w, nxt, flow;
}edge[M];

void add(int u, int v , int c, int w){
	edge[tot].w = w;
	edge[tot].flow = c;
	edge[tot].to = v;
	edge[tot].nxt = head[u];
	head[u] = tot ++ ;
}
void ADD(int u, int v, int c, int w){
	add(u, v, c, w);
	add(v, u, 0, -w);
}

int incf[N], vis[N], d[N], pre[N];
bool spfa(){
	memset(d, 0x3f, sizeof d);
	memset(incf, 0, sizeof incf);
	memset(vis, 0, sizeof vis);
	
	queue<int > Q;
	Q.push(S);
	vis[S] = true;
	incf[S] = INF;
	d[S] = 0;
	while(Q.size()){
		int u = Q.front();
		Q.pop();
		vis[u] = false;
		for(int i = head[u]; ~i; i = edge[i].nxt){
			int v = edge[i].to;
			if(d[v] > d[u] + edge[i].w && edge[i].flow){
				d[v] = d[u] + edge[i].w;
				incf[v] = min(incf[u], edge[i].flow);
				pre[v] = i;
				if(!vis[v]){
					vis[v] = true;
					Q.push(v);
				}
			}
			
		}
	}
	return incf[T] > 0;
}

int EK(int &flow, int &ans){
	flow = 0;
	ans = 0;
	while(spfa()){
		int t = incf[T];
		flow += t;
		ans += t * d[T];
		for (int i = T; i != S; i = edge[pre[i] ^ 1].to){
            edge[pre[i]].flow -= t;
            edge[pre[i] ^ 1].flow += t;
        }
	}
}
int main(){
	memset(head, -1, sizeof head);
	scanf("%d %d %d %d",&n, &m, &S, &T);
	for(int i = 1;i <= m;i += 1){
		scanf("%d %d %d %d", &u, &v, &c, &w);
		add(u, v, c, w);
		add(v, u, 0, -w);
	}
	int flow ,ans;
	EK(flow, ans);
	printf("%d %d",flow, ans);
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值