#0 一些定义
对于带权有向图 :
- 定义 为一条权值为 的从 连向 的有向边;
- 定义 为图 的点集, 为 的边集;
#1 什么是网络流
定义 1.1(网络、源点汇点、容量)
定义网络 如下:
- 是一个边带非负权的有向图, 和 是 中的节点;
- 被称为源点, 被称为汇点;
- 对于 中的边 , 被称为改边的容量;
定义 1.2(流、流量)
对于一个网络 ,定义一组流 ,其中 为第 条边被使用的流量,需要满足:
- ,有 ;
- ,有 ,即流入该点的流量和等于流出该点的流量和;
定义流 的流量 为 ,即流到汇点 的流量和。
形象地:
对于一个带权有向图 及图上的两个点 (源点)和 (汇点),可以这样看待这张有向图:
- 将点看作水池,边看作水管,则有向边 表示单位时间内最多 单位的水可以通过该水管从水池 流向水池 ;
- 源点 是水源,单位时间内能提供无限单位的水;
- 汇点 是排水口,单位时间内能接受无限单位的水;
流就是让水从水源流到排水口而不爆水管的方案,流量就是方案中从排水口排走的水量。
#2 网络最大流
定义 2.1(有源汇最大流)
对于一个网络 ,定义其上的最大流 为一组满足 最大的流 。
严格的定义理应是满足 最大的 构成的集合,简便起见就不这样记了。
例如对于这个网络:

其一组最大流(流量为 )如下:(边权为该边使用的流量)

2.1 最大流的求解
2.1.1 反向边的引入
考虑贪心,不断找到从 到 且容量均 的路径(增广路),即通过这条路径可以让至少 的流量从 流到 ,那么将答案增加 ,并将路径上边的容量全部减少 。
定义 2.1.1(增广路)
定义一条增广路为一条从 到 路径,满足路径上边的容量均 。
定义 2.1.2 pre(增广)
对于一条增广路 ,定义增广操作:
- 令 ,即 中的边的最小容量;
- 令 中边的容量均减少 ;
- 令答案(最大流大小)增加 ;
那么上述贪心实际上就是不断找增广路进行增广的过程。
但这个贪心是错的,对于一个这样的最大流大小为 的网络:

贪心会求出这样的最大流,大小为 :

因此参考反悔贪心,引入反悔(退流)思想。
具体的,对于每条边 ,建立反向边 :

并修改增广的定义:
定义 2.1.2(增广)
对于一条增广路 ,定义增广操作:
- 令 ,即 中的边的最小容量;
- 令 中边的容量均减少 ,令 中边对应的反向边的容量均增加 ;
- 令答案(最大流大小)增加 ;
例如对于例子中的网络,进行一轮新的增广后是这样的:

不难发现现在网络中还存在增广路,还能进行增广操作:

那么修正后的贪心就成功求出了一组最大流,最终每条边反向边的容量即为这组最大流中该边使用的流量。
这其实是一个反悔贪心,增广过程中走反向边相当于退流。
容易证明,不断重复上述操作一定能求出一组最大流。
2.1.2 EK 算法
直接模拟上述过程,可以得出求解网络流的朴素算法。
建完图后,用 bfs 来找增广路,并记录下每个点的前驱节点(令其入队的点)以便找到增广路上的边。如果 bfs 时访问到了汇点,那么就找到了一条增广路。
为了储存反向边,不妨令边的编号从 开始,并令编号为 的边和编号为 的边互为反向边。这样实现起来会方便很多。
不断跑 bfs 找增广路去进行增广操作直到 和 不连通,即可求解出网络的一组最大流。该算法被称作 EK 算法,时间复杂度是 的,其中 是最大流的大小,即值域。
2.1.3 dinic 算法
dinic 算法是 EK 算法的一种改进,其可以同时增广多条增广路。
其大体流程如下:
- 每次增广前先跑一次 bfs,将节点按照距离 的最短路(边数) 分层,增广时要求增广路相邻点的 恰好相差 ;
- 接下来跑 dfs,对所有满足条件的增广路同时进行增广操作;
- 不断重复上两步直至 bfs 时 不能到达 ,即不存在增广路;
伪代码如下:
int dfs(int u,int w) // u 表示当前节点的编号,w 表示流向当前点的流量,返回值为成功增广的流量
{
int sum=0;
遍历所有从 u 出发的边 i
{
int v=i 到达的节点;
if(dep[v]==dep[u]+1)
{
int re=dfs(v,min(w,i 的容量));
if(re!=0)
{
更新正向边和反向边的容量;
w-=re;
sum+=re;
}
}
}
return sum;
}
int dinic()
{
int res=0;
while(bfs 可以到达 汇点) res+=dfs(s,inf);
return res;
}
此外,dinic 算法还有几个优化,分别是当前弧优化和断层优化:
- 当前弧优化:把已经“榨干”(容量为 )的边删掉;
- 断层优化:把已经“榨干”的节点“删掉”(
return
时若sum==0
则令dep[u]=0
);
一般默认会加上这两个优化。
dinic 的最劣复杂度为 ,证明考虑每次找到的增广路边数一定最小,故 递增;而单次 dfs 中每条增广路都会删掉至少一条边(当前弧优化)故最多有 条增广路,而每条增广路长度至多为 。
并且 dinic 在容量均为 的网络上的复杂度为 ,具体证明及更多关于复杂度的信息可以看Dinic的几种复杂度 - myee - 博客园。
但由于网络流题目的图一般都有特殊性质,故加上优化后的 dinic 常常十分快速,基本不会被卡,在 OI 中最常用。
求解网络流还有 ISAP 和 HLPP,由于太过复杂且 OI 中不考,故不再介绍。
#3 最小费用最大流
在最小费用最大流问题中,每条边新增了一个单位代价 ,表示这条边流过 流量需要 的代价,需要求解一组总代价最小的最大流。
也可以使用 dinic 算法解决,每次增广单位代价最小的增广路即可,正确性显然。建边时反向边代价为 ,而且 bfs 需要换成 spfa,且增广时要求增广路相邻点的 恰好相差它们之间的边的代价。
值得注意的是,由于代价可能为 ,所以 dfs 时需要防止重复访问点。
#4 有向图最小割
定义 4.1(有向图的割)
对于一个带权有向图 及图上的两个点 和 ,定义一组 到 的割 如下:
- ;
- 删掉 中的边后 不能到达 ;
定义割 的大小 为 ,即割中的边权和。
定义 4.2(有向图的最小割)
对于一个带权有向图 及图上的两个点 和 ,定义其最小割 为 最小的一组割 。
定理 4.1(最大流等于最小割)
对于一个带权有向图 及图上的两个点 和 ,有:
证明很简单,首先由于跑完最大流算法后 不能到达 ,故 ,又显然 (最大流中满流的边集构成割),故得证。
更详细的证明可以自行上网寻找。
并且根据一组最大流 ,可以简单地构造出最小割:
- 令每条边的剩余容量 为 ;
- 从 出发,只经过 的边,求出能到达的点集 ;
- 一组最小割 即为 ,即一端在 中的边的集合;
#5 有上下界网络流
5.1 有上下界循环可行流
个点, 条有向边,每条边有一个流量下界 和流量上界 ,求该图的一个循环可行流,即你需要构造 个变量 满足:
- 流量合法:;
- 流量平衡:对于每个点 ,设 为 的入边集合, 为 的出边集合,那么有:;
考虑每条边先钦定流过 的流量,转化为每条边只有流量上界 ,那么流量有可能不平衡。
考虑用点与源点 或汇点 的连边刻画每个点的额外流量。记 ,即每个点进入的流量减出去的流量,那么:
- 若 ,连接边 ;
- 若 ,连接边 ;
原先的边 直接连 。
跑最大流,若所有 和 均满流,则有解,否则无解。设跑完后第 条边的流量为 ,则第 条边的流量为 。
时间复杂度和最大流相同。
5.2 有上下界有源汇最大流
考虑将其转化为循环流来消去上下界,设源点为 ,汇点为 ,则不难发现相当于从 出发的流量等于到达 的流量,连边 。
跑出循环流后,记 为 的。接下来把 去掉,设第 条边在循环流中流过了 流量,则连接边 时:
- 正向边流量为 ;
- 反向边流量为 ;
则答案即为 加上当前图从 到 的最大流。
5.3 有上下界有源汇最小流
最大流是尽可能从 流到 ,那么最小流就是尽可能从 退回 。
跑出 到 的最大流 ,答案即为 。