费用流
问题
给定一个包含 n n n 个点 m m m 条边的有向图,并给定每条边的容量和费用,边的容量非负。
图中可能存在重边和自环,保证费用不会存在负环。
求从 S S S 到 T T T 的最大流,以及在流量最大时的最小费用。
分析
首先大胆假设,在残留网络上沿着最短路(边权即费用)增广,直到得到最大流(无法再增广),那么,假如图中没有负环,这样的最大流的费用是最小的。
证明
下面证明正确性:
假如存在具有相同流量比
f
f
f 费用更小的流
f
’
f’
f’ ,考察
f
’
−
f
f’-f
f’−f ,可知它由若干个圈组成,因为
f
’
−
f
f’-f
f’−f 的费用为负,故在这些圈中至少存在一个负环。
也就是说, f f f 是最小费用流 ⇔ \Leftrightarrow ⇔ 残留网络中不存在负环
下面用归纳法论证:
- 对于流量为 0 0 0 的流 f 0 f_0 f0 ,残留网络即为原图,故只要不存在负环,那么 f 0 f_0 f0 就是最小费用流。
- 下证:若流量为 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+1−fi 对应的路径又是 s → t s\rightarrow t s→t 的最短路,所以 f i + 1 ’ − f i f_{i+1}’-f_i fi+1’−fi 对应的路径一定存在负环,与 f i f_i fi 为最小费用流矛盾。
那么,如果图中存在负环,如何解决呢?
此时我们需要对问题进行转化,从 t → s t\rightarrow s t→s 连一条容量为 ∞ ∞ ∞ ,费用为 − - − 的虚边得到无源汇流。
注意到如果存在增广路,则一定可以通过这条路径以及虚边使得费用更小(走完后再走一个 − ∞ -∞ −∞),反之,如果不存在增广路,则在原图相应地得到了最小费用最大流。
下面只需求无源汇流的最小费用。
我们采取 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
⌊2k−1c(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);
}