网络流
基本定义
首先我们定义一个名为“网络”的有向图,并将其边权命名为“容量”,额外的,我们有一个“源点”和一个“汇点”
流,顾名思义,就像水流或电流,也具有它们的性质。如果把网络想象成一个管道网络,那流就是其中流动的水。每条边上的流不能超过它的容量,并且对于除了源点和汇点外的所有点(即中继点),流入的流量都等于流出的流量。
问题
给定一个网络,求其最大流?
分析
我们先引入一个概念,名为增广路。其定义为由源点到汇点的一条路径,且残余容量大于零。为何说是残余容量呢?因为我们每找到一条增广路,增广路上的每条边就要减去流量,故称残余容量。
而我们经过一次次增广(寻找增广路),在将所有流量相加就可求出最大流了。
但是!
比如这张图,我们如果先增广 1->2->3->4这条路径的话,我们将得到 1 1 1 的流,而显然,如果我们分别增广 1->3->4 和 1->2->4这两条路径的话,我们可以得到 2 2 2 的流。
而这怎么解决的呢?
我们可以对每一条边建立一条反边,如下图:
然后这时候我们在这个网络上进行增广,并将流量加给反边,原本的问题我们惊奇的发现可以解决了!
那我们来理解一下,残余流量就是容量减去流量,而我们把流量加到了反边上,所以我们在使用反边时,就给正边加上流量,也就相当于没有用这条边——即为「反悔」
FF算法
讲完解法,我们看算法
首先便是FF算法,这个算法通过DFS查找增广路,上代码:
int n, m, S, T; // S是源点,T是汇点
bool vis[N];
int dfs(int u = S, int limit = INF){
if(u == t)
return limit; // 到达终点,返回这条增广路的流量
vis[p] = true;
for(int i = head[p]; ~i; i = edge[i].nxt){
int to = edge[i].to, vol = edge[i].w, c;
// 返回的条件是残余容量大于0、未访问过该点且接下来可以达到终点(递归地实现)
// 传递下去的流量是边的容量与当前流量中的较小值
if(vol > 0 && !vis[to] && (c = dfs(to, min(vol, flow))) != -1){
edge[i].w -= c;
edge[i ^ 1].w += c;
// 这是链式前向星取反向边的一种简易的方法
// 建图时要把第一条边的编号设置为偶数,且要保证反向边紧接着正向边建立
return c;
}
}
return -1; // 无法到达终点
}
inline int FF(){
int ans = 0, c;
while((c = dfs()) != -1){
memset(vis, 0, sizeof(vis));
ans += c;
}
return ans;
}
用dfs实现的FF算法时间复杂度上界是 O ( e f ) O(ef) O(ef),其中 e e e 为边数, f f f 为最大流
(因复杂度问题一般不用)