图论的证明太复杂了,就只说一下算法流程理解一下好了。
一. 相关定义
- 容量网络:设 G ( V , E ) G(V,E) G(V,E),是一个有向网络,在 V V V中指定了一个顶点,称为源点(记为 V s V_s Vs),以及另一个顶点,称为汇点(记为 V t V_t Vt);对于每一条弧 ( u , v ) (u,v) (u,v)属于 E E E,对应有一个权值 c ( u , v ) > 0 c(u,v)>0 c(u,v)>0,称为弧的容量。通常把这样的有向网络 G G G称为容量网络。
- 弧的流量:通过容量网络 G G G中每条弧 ( u , v ) (u,v) (u,v)上的实际流量(简称流量),记为 f ( u , v ) f(u,v) f(u,v);
- 反向弧:当一个弧流过 f ( u , v ) f(u,v) f(u,v)的流量时,我们建立一条 ( v , u ) (v,u) (v,u)的反向弧,并把反向弧的容量增加 f ( u , v ) f(u,v) f(u,v),当流量在反向弧上流过时,再把此流量增加到 ( u , v ) (u,v) (u,v)的容量当中。
- 网络流:所有弧上流量的集合 f = { f ( u , v ) } f=\{f(u,v)\} f={f(u,v)},称为该容量网络的一个网络流。
- 可行流:在容量网络
G
G
G中满足以下条件的网络流
f
f
f:
a. 容量限制: 0 ≤ f ( u , v ) ≤ c ( u , v ) 0\le f(u,v)\le c(u,v) 0≤f(u,v)≤c(u,v);
b. 流量平衡:除源点和汇点外,流入一个点的流量要等于流出这个点的流量;
c. 斜对称性: f ( u , v ) = − f ( v , u ) f(u,v)=-f(v,u) f(u,v)=−f(v,u)。 - 最大流:在容量网络中,具有最大流量的可行流,称为网络最大流,简称最大流。
- 残量网络:网络中的每条边(包括反向弧)上容量与流量之差构成残量网络。
- 增广:残量网络中任何一条从 s s s到 t t t的有向道路中所有残量的最小值 d d d,把对应的所有边上的流量增加 d d d,这个过程叫做增广。
- 增广路定理:网络达到最大流当且仅当残留网络中没有增广路。
二. 最大流
1.反向边
反向边的意义在于,可以处理再找增广路的时候撤回之前路的流量。
设一条边为
(
u
,
v
)
(u,v)
(u,v),则其反向边为
(
v
,
u
)
(v,u)
(v,u)。
实质上,每走一次
(
v
,
u
)
(v,u)
(v,u)相当于本次增广的路径中,从
v
v
v到汇点的路径走原来流过
(
u
,
v
)
(u,v)
(u,v)之后到从
v
v
v到汇点的路径,然后我们再去给原来那条走
(
u
,
v
)
(u,v)
(u,v)的路径从
u
u
u点找一条其他的到汇点的路径,实现增广。
左图是一个残量网络,右图是一个走了反向边的增广路,具体来说,原来路径为
1
→
2
→
3
→
4
1\to 2 \to 3 \to 4
1→2→3→4,现在在
3
3
3走反向边的实质为:本次的增广路(红色)
1
→
3
1\to 3
1→3后面的路走原来
1
→
2
→
3
→
4
1\to 2 \to 3 \to 4
1→2→3→4路径中的
3
→
4
3\to 4
3→4,然后我们从
2
2
2开始,再找一条到
4
4
4的路,来代替
1
→
2
→
3
→
4
1\to 2 \to 3 \to 4
1→2→3→4。
2.增广路算法
只要我们不停的找从源点到汇点的增广路,根据增广路定理,当我们找完了所有的增广路,就可以得到最大流了。
- 首先我们可以从源点开始,不断的进行dfs,每次找一个残量网络中能到达汇点的路线,给路线上所有的边减去路线上的最小的残余流量,这是 FF \text{FF} FF算法,时间复杂度为 O ( V E ) O(VE) O(VE),其中 V V V是最大流, E E E是边数。
- 我们如果通过 BFS \text{BFS} BFS找到一条从源点到汇点的最短的增广路,并将其增广,可以证明最多需要增广 n × m n\times m n×m,次这就是 EK \text{EK} EK算法,时间复杂度为 O ( n m 2 ) O(nm^2) O(nm2)。
- 很显然, BFS \text{BFS} BFS一次来找一条最短的增广路比较浪费,所以我们考虑进行一次 BFS \text{BFS} BFS后,将图进行分层,这样如果我们按照分的层来找增广路,找到的增广路一定是最短的;在分层的基础上,我们用 DFS \text{DFS} DFS进行增广,直到找不到增广路为止;然后我们继续给新的残量网络分层,继续操作。这个算法名叫 Dinic \text{Dinic} Dinic。不难发现,每进行一次分层,从源点到汇点的层数至少加 1 1 1,所以我们最多分 n − 1 n-1 n−1次层,每次找增广路的复杂度为 O ( n m ) O(nm) O(nm),所以 Dinic \text{Dinic} Dinic的时间复杂度上限为 O ( n 2 m ) O(n^2m) O(n2m)。可以证明,对于二分图 Dinic \text{Dinic} Dinic的时间复杂度为 O ( n m ) O(\sqrt{n}m) O(nm)。
- 关于Dinic的当前弧优化(效果显著的常数优化)
在一次dfs的过程中,如果一个点的一条边的流量用完了,那下一次在访问到这个点的时候就没有必要再次访问这条边了,我们只需要记录现在访问到的弧的位置,并在当前结单流量用完后及时跳出循环,下次用记录的位置开始,就是当前弧优化。
三.最小割
最小割定义为找一组边权最小的边集,并去掉,让
S
S
S与
T
T
T不连通。
有最大流最小割定理可以证明,最大流等于最小割。
四.最小费用最大流
在保证最大流量的基础上,有很多种方案满足要求,如果每条边还有一个边权,若总费用为所有边的边权与流量的乘积之和,最小费用最大流就是总费用最小的方案。
关于算法,直接把
EK
\text{EK}
EK算法的
BFS
\text{BFS}
BFS换成
SPFA
\text{SPFA}
SPFA,每次走最短路进行增广。
#include <cstdio>
#include <algorithm>
const int inf = 1e9;
const int maxn = 200010;
int cnt = 1;
int head[maxn], nxt[maxn], to[maxn], fl[maxn], v[maxn];
int q[maxn], dis[maxn], S, T, n, m, MX;
int pre[maxn], epre[maxn];
bool vis[maxn];
void add(int a, int b, int c, int d){
nxt[++ cnt] = head[a], head[a] = cnt, to[cnt] = b, fl[cnt] = c, v[cnt] = d;
nxt[++ cnt] = head[b], head[b] = cnt, to[cnt] = a, fl[cnt] = 0, v[cnt] = -d;
}
bool spfa(){
for(int i = 1; i <= MX; i ++) dis[i] = inf, vis[i] = 0;
vis[S] = 1, dis[S] = 0;
int l = 1, r = 0;
q[++ r] = S;
while(l <= r){
int x = q[l ++];
for(int i = head[x]; i; i = nxt[i]){
int u = to[i];
if(fl[i] > 0 && dis[u] > dis[x] + v[i]){
dis[u] = dis[x] + v[i];
pre[u] = x, epre[u] = i;
if(vis[to[i]] == 0) vis[to[i]] = 1, q[++ r] = to[i];
}
}
vis[x] = 0;
}
if(dis[T] != inf) return true;
return false;
}
void work(){
int flow = 0, cost = 0;
while(spfa()){
int f = inf;
for(int x = T; x != S; x = pre[x]){
if(fl[epre[x]] < f) f = fl[epre[x]];
}
cost += f*dis[T], flow += f;
for(int x = T; x != S; x = pre[x]){
fl[epre[x]] -= f, fl[epre[x]^1] += f;
}
}
printf("%d %d\n", flow, cost);
}
int main(){
scanf("%d%d%d%d", &n, &m, &S, &T);
MX = n;
for(int i = 1; i <= m; i ++){
int a, b, c, d;
scanf("%d%d%d%d", &a, &b, &c, &d);
add(a, b, c, d);
}
work();
return 0;
}
五.网络流建模
1.最大流
(1)每个人选两个不同种类的物品
描述:一群人都要分别从A类物品中选一个,B类物品中选一个,求最多可以满足多少人。
做法:为了保证一个人只从一类物品中选择一个,要分别把两类物品与
S
S
S与
T
T
T相连,容量为物品数量。为了保证一个人只能分别取一样,要把一个人拆成两个点,两点之间容量为
1
1
1。人放在中间,两类物品放在两边。
例1:Dining
(2)二分图最大匹配
描述:求一个二分图的最大匹配。
做法:源点和汇点分别连接一侧的点,容量为
1
1
1,原来的二分图每条边,容量为
1
1
1。
例1:A Plug for UNIX
2.最小割
最小割建模考虑把现在已有的点分为两部分,构造连边使分割方法对应相应的代价。