网络流的定义
网络流(network-flows)是一种类比水流的解决问题方法,与线性规划密切相关。
引入
在具体讲解网络流之前,我们用一个例子来理解一下:
假设有一个自来水厂,自来水厂无限量地通过一条条水管向每家每户运输自来水,而我们在用完自来水后的废水也要通过一些管道流向一个废水站(容量可视为无穷大),管道有一定容量,问废水站最多可以收集多少水。
易知这是一个有向图。
那么解决这个问题的经典算法就是——网络流。
概念明确
容量:每条边最大的流量(上图边中间的数字,水管的容量)
源点:起点(上图中 号点,自来水厂)
汇点:终点(上图中 号点,废水站)
流:从源点到汇点的一条合法路径(上图中 ,自来水厂 家 废水站)
流量:每条边各自被经过的次数(上图中 给每条边增加了 的流量)
限制
每条边的流量不超过容量。
每条边的流入量等于流出量。
实现(増广路方法)
FF算法
找到一条从源点到汇点的路径,我们称其为増广路。
找到流过的路径剩余流量的最小值,记为
将答案加上
将该路径上所有边的剩余流量减去 ,其反向边的剩余流量加上 。
重复上述步骤知道找不到路径为止。
会被极端数据卡死。
EK算法
对FF算法的优化,每次找最短路进行増广。
时间复杂度上界
大多数情况跑不满上界。
Dinic算法
EK的复杂度太高,需要优化。
先给每条边赋一个 的权值,bfs找到每个点到源点的最短路。
如果源点到汇点的最短路不存在,即不存在増广路,退出。
dfs找所有的増广路,进行一些操作。
显然的是,如果増广一次后最短路没有任何变化,那么我们还是可以继续使用那条最短路,就不用bfs了。
时间复杂度:
二分图最大匹配时间复杂度(所有边容量为 ):
ISAP(Improved Shortest Augumenting Path)算法
这个是今天的重点。
我们观察到Dinic算法每次dfs是都要进行一次bfs,那么我们有没有办法进一步优化呢?
当然有。
这就是我们的ISAP算法。
观察到每一次求増广路时对我们的层次图改变不大,那么我们就只求一次层次图,在dfs里直接修改层次图来优化时间复杂度。
实现
从汇点到源点跑一遍bfs,找出最短路。
从源点到汇点进行dfs,如果一个点从前面的点传入的流量大于其流出的流量,那么这个点在当前深度就没有用处了,我们将深度++。
重复上述操作直到出现断层,即某个深度没有点,那么就不存在増广路,直接退出。
下面用代码加深理解。
#include<bits/stdc++.h>
using namespace std;
int n,m,s,t;
int tot=1,first[200005],nxt[200005],to[200005],w[200005],dep[200005],cnt[200005];
int Read(){
int x=0,f=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-') f=-1;
ch=getchar();
}
while(isdigit(ch)){
x=(x<<3)+(x<<1)+ch-'0';
ch=getchar();
}
return x*f;
}
void Add(int x,int y,int z){
nxt[++tot]=first[x];
first[x]=tot;
to[tot]=y;
w[tot]=z;
}
void Bfs(int t){
memset(dep,0xff,sizeof(dep));
dep[t]=0;
cnt[0]=1;
queue<int> q;
q.push(t);
while(!q.empty()){
int u=q.front();
q.pop();
for(int e=first[u];e;e=nxt[e]){
if(dep[to[e]]==-1){
dep[to[e]]=dep[u]+1;
++cnt[dep[to[e]]];
q.push(to[e]);
}
}
}
}
int mf=0;
int dfs(int p,int f){
if(p==t){
mf+=f;
return f;
}
int u=0;
int e;
for(e=first[p];e;e=nxt[e]){
if(w[e]&&dep[to[e]]==dep[p]-1){
int uu=dfs(to[e],min(w[e],f-u));
if(uu){
w[e]-=uu;
w[e^1]+=uu;
u+=uu;
}
if(u==f) return u; //如果流入等于流出,那么就结束
}
}
//此时流入必定大于流出,我们操作一下这个点
if(!--cnt[dep[p]]){ //出现断层
dep[s]=n+1;
}
++cnt[++dep[p]];
return u;
}
signed main(){
n=Read(),m=Read(),s=Read(),t=Read();
for(int i=1;i<=m;i++){
int U=Read(),V=Read(),W=Read();
Add(U,V,W);
Add(V,U,0);
}
Bfs(t);
while(dep[s]<n){
dfs(s,0x3fffffff);
}
cout<<mf<<endl;
return 0;
}
关于ISAP和Dinic的复杂度
二分图Dinic快,一般图ISAP快。
理论复杂度上界一样。