最大流
网络流初步
对于一张N个点、M条边的有向图(网络),以及源点S和汇点T。
边的容量
现将\(c(x,y)\)定义为每条边能通过的最大容量。
流函数
流函数\(f(x,y)\)定义为边的流量。显然,它满足三个条件
-
\(f(x,y)=-f(y,x)\)即正向边的流量=反向边的流量。
-
\(f(x,y)≤c(x,y)\)即每条边的流量不可能大于该边的容量。
-
对于所有的非源点和汇点的结点u,v:\(\sum_{(u,v)\in E}f(u,v) = \sum_{(v,u)\in E}f(v,u)\),即正向边的所有流量和=反向边的流量和。
链
在容量网络中,称顶点序列\((u_1,u_2,u_3,u_4,…,u_n,v)\)为一条链要求相邻的两个顶点之间有一条弧.
设P是容量网络G中一条从Vs到Vt的链,约定从Vs指向Vt的方向为正方向.在链中并不要求所有的弧的方向都与链的方向相同.
-
前向弧:(方向与链的正方向一致的弧),其集合记为P+,
-
后向弧:(方向与链的正方向相反的弧),其集合记为P-.
增广路
设f是一个容量网络G中的一个可行流,P是从Vs到Vt 的一条链,若P满足以下条件:
- P中所有前向弧都是非饱和弧。
- P中所有后向弧都是非零弧。
则称P为关于可行流f 的一条增广路。
沿这增广路改进可行流的操作称为增广。
残余网络
残余网络 (Residual Network) :在一个网络流图上,找到一条源到汇的路径(即找到了一个流量)后,对路径上所有的边,其容量都减去此次找到的量,对路径上所有的边,都添加一条反向边,其容量也等于此次找到的流量,这样得到的新图,就称为原图的“残余网络”。
假定有一个流网络G=(V,E),其源点为s,汇点为t,f为G中的一个流。对即诶点对u,v,定义残存容量\(cf(u,v)=c(u,v)-f(u,v)\)。
零流
若网络流上每条弧上的流量都为0,则该网络流称为零流.
网络流
所有弧上流量的集合f={f(u,v)},称为该容量网络的一个网络流.
弧
容量网络的边称为弧,弧的类型有:
- 饱和弧:\(f(u,v)=c(u,v)\)
- 非饱和弧:\(f(u,v)<c(u,v)\)
- 零流弧:\(f(u,v)=0\)
- 非零流弧:\(f(u,v)>0\)
可行流:在容量网络G中满足以下条件的网络流f,称为可行流。
a.弧流量限制条件: 0<=f(u,v)<=c(u,v);
b.平衡条件:即流入一个点的流量要等于流出这个点的流量,(源点和汇点除外);
c.斜对称性:f(u,v) = -f(v,u);
对于网络流图G,流量最大的可行流f,称为最大流。
伪流: 如果一个网络流只满足弧流量限制条件,不满足平衡条件,则这种网络流为伪流,或称为容量可行流.(预流推进算法有用)
反向边的引入
下面是所有最大流算法的精华部分:引入反向边
举个例子,在下图,流量图的源点为1,汇点为4。

我们第一次找到了1-2-3-4这条增广路,这条路上的delta值显然是1。于是我们修改后得到了下面这个流。(图中的数字是容量)

这时候(1,2)和(3,4)边上的流量都等于容量了,我们再也找不到其他的增广路了,当前的流量是1。
但这个答案明显不是最大流,因为我们可以同时走1-2-4和1-3-4,这样可以得到流量为2的流。
那么我们刚刚的算法问题在哪里呢?问题就在于我们没有给程序一个后悔的机会,应该有一个不走(2-3-4)而改走(2-4)的机制。那么如何解决这个问题呢?回溯搜索吗?那么我们的效率就上升到指数级了。
而这个算法神奇的利用了一个叫做反向边的概念来解决这个问题。即每条边\((x,y)\)都有一条反向边\((y,x)\),反向边也同样有它的容量。
在第一次找到增广路之后,在把路上每一段的容量减少delta的同时,也把每一段上的反方向的容量增加delta。即
c(x,y)-=delta;c(y,x)+=delta
我们来看刚才的例子,在找到1-2-3-4这条增广路之后,把容量修改成如下:

这时再找增广路的时候,就会找到1-3-2-4这条可增广量,即delta值为1的可增广路。将这条路增广之后,得到了最大流2。

那么,这么做为什么会是对的呢?
事实上,当我们第二次的增广路走3-2这条反向边的时候,就相当于把2-3这条正向边已经是用了的流量给”退”了回去,不走2-3这条路,而改走从2点出发的其他的路也就是2-4。(有人问如果这里没有2-4怎么办,这时假如没有2-4这条路的话,最终这条增广路也不会存在,因为他根本不能走到汇点)同时本来在3-4上的流量由1-3-4这条路来”接管”。而最终2-3这条路正向流量1,反向流量1,等于没有流量。
这样,利用反向边,使程序有了一个后悔和改正的机会
文章引用:
https://blog.csdn.net/CY05627/article/details/90552189
最大流
Edmonds−Karp算法(EK算法)
时间复杂度O(\(nm^2\))
思想:不断用BFS寻找增广路并不断更新最大流量值,直到网络上不存在增广路为止。
// Luogu P3376 【模板】网络最大流
#include <bits/stdc++.h>
#define LL long long
#define N 10010
#define M 200010
using namespace std;
int n,m,S,T;
struct edge{LL v,c,ne;}e[M];
int h[N],idx=1;//从2,3开始配对
LL mf[N],pre[N];//mf存储s-v的路径上的流量上限,pre存储点的前驱边。
void add(int a,int b,int c){
e[++idx]={b,c,h[a]};
h[a]=idx;
}
bool bfs(){//找增广路
memset(mf,0,sizeof mf);
queue<int> q;
q.push(S); mf[S]=1e9;
while(q.size()){
int u=q.front();
q.pop();
for(int i=h[u];i;i=e[i].ne){
LL v=e[i].v;
if(mf[v]==0 && e[i].c){
mf[v]=min(mf[u],e[i].c);
pre[v]=i;//存前驱边
q.push(v);
if(v==T)return true;//找到一条增广路
}
}
}
return false;
}
LL EK(){//累加可行流
LL flow=0;
while(bfs()){
int v=T;
while(v!=S){//更新残留网
int i=pre[v];
e[i].c-=mf[T];
e[i^1].c+=mf[T];//反向边
v=e[i^1].v;
}
flow+=mf[T];
}
return flow;
}
int main(){
int a,b,c;
scanf("%d%d%d%d",&n,&m,&S,&T);
while(m--){
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
add(b,a,0);//反向边
}
printf("%lld\n",EK());
return 0;
}
Dinic算法
进行BFS建立分层图,用d[x]记录每一个点的层次
求解最大流的时间复杂度为\(O(n^2m)\)
求解二分图最大匹配的时间复杂度为\(O(m\sqrt{n})\)
当前弧优化
对于一个节点x,当它在DFS中走到了第i条弧时,前i−1条弧到汇点的流一定已经被流满而没有可行的路线了
那么当下一次再访问x节点时,前i−1条弧就没有任何意义了
所以我们可以在每次枚举节点x所连的弧时,改变枚举的起点,这样就可以删除起点以前的所有弧,来达到优化剪枝的效果。
// Luogu P3376 【模板】网络最大流
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#define LL long long
#define N 10010
#define M 200010
using namespace std;
int n,m,S,T;
struct edge{LL v,c,ne;}e[M];
int h[N],idx=1; //从2,3开始配对
int d[N],cur[N];
void add(int a,int b,int c){
e[++idx]={b,c,h[a]};
h[a]=idx;
}
bool bfs(){ //对点分层,找增广路
memset(d,0,sizeof d);
queue<int>q;
q.push(S); d[S]=1;
while(q.size()){
int u=q.front(); q.pop();
for(int i=h[u];i;i=e[i].ne){
int v=e[i].v;
if(d[v]==0 && e[i].c){
d[v]=d[u]+1;
q.push(v);
if(v==T)return true;
}
}
}
return false;
}
LL dfs(int u, LL mf){ //多路增广
//mf是整条路对最大流的贡献
if(u==T) return mf;
LL sum=0;
for(int i=cur[u];i;i=e[i].ne){
cur[u]=i; //当前弧优化
int v=e[i].v;
if(d[v]==d[u]+1 && e[i].c){
LL f=dfs(v,min(mf,e[i].c));
e[i].c-=f;
e[i^1].c+=f; //更新残留网
sum+=f; //累加u的流出流量
mf-=f; //减少u的剩余流量
if(mf==0)break;//
}
}
if(sum==0) d[u]=0; //残枝优化
return sum;
}
LL dinic(){ //累加可行流
LL flow=0;
while(bfs()){
memcpy(cur, h, sizeof h);
flow+=dfs(S,1e9);
}
return flow;
}
int main(){
int a,b,c;
scanf("%d%d%d%d",&n,&m,&S,&T);
while(m -- ){
scanf("%d%d%d",&a,&b,&c);
add(a,b,c); add(b,a,0);
}
printf("%lld\n",dinic());
return 0;
}

浙公网安备 33010602011771号