一些定义

  1. 流网络:有向图,没有反向平行边,源点ss和汇点tt分别只有一个

  2. 流:一个实值函数f:V×VRf: V\times V \rightarrow \mathbb{R},并且满足以下两条性质:

    1. 容量限制:0f(u,v)c(u,v)0 \le f(u, v) \le c(u, v)
    2. 流量守恒:vVf(u,v)=vvf(v,u)\sum_{v\in V}f(u, v) = \sum_{v\in v}f(v,u)

    最大流问题中,我们想找到最大的f(s,t)f(s,t)

两个简单的推广

  1. 如果有反向平行边呢?

    很简单,本来是u->v, v->u,现在添加一个结点u',把图转化为u->v, v->u', u'->u,就去掉了反向平行边

  2. 如果有多个源和汇呢?

    也不难,添加一个超级源和超级汇,从其出发的有向边指向真实的源点,并且容量无限大;汇点同理。

Ford-Fulkerson方法的准备

  1. 残存网络GfG_f:给原图添加反向平行边,其容量和正向边的流一样大。原图的正向边容量改为其原容量与流的差(如果为算出新的容量为0,那么就认为这个边不存在),这样一操作,得到的就是所谓的残存网络GfG_f

    更正式地:

    cf(u,v)={c(u,v)f(u,v)(u,v)Ef(u,v)(v,u)E0Otherwise c_f(u, v) = \begin{cases} c(u, v) - f(u, v) & (u,v)\in E \\ f(u, v) & (v,u)\in E \\ 0 & \text{Otherwise} \end{cases}

    反向边的存在使得我们在需要的时候可以反向推流,也就是所谓抵消操作,来达到全局最优解。

  2. 增广路径:残存网络中从源点到汇点的一条简单路径。易知

    cf(p)=min(u,v)pcf(u,v) \displaystyle c_f(p) = \min_{(u,v)\in p}c_f(u, v)

  3. 找到一条增广路径,我们据此增加原图中的流,然后再更新残存网络GfG_f。这作为一次增广操作。

  4. 流网络的切割:将网络的结点分为两个集合SSTT,其中sSs \in StTt\in T,并且S+T=VS + T = V(此式蕴含了SSTT不相交)

  5. 横跨切割的净流量:连接SSTT的边的流量的和。(如果是反流回来的,那么要加个负号)

  6. 切割的容量:连接SSTT的边的容量和

根据上面的定义,可以推得最大流最小割定理,也就是最大流的大小和最小的切割容量相等。更具体地,下面三个结论等价:

  1. ffGG的最大流
  2. GfG_f没有增广路径
  3. f=c(S,T)\vert f\vert = c(S, T)

Ford-Fulkerson方法

简单来说:一直增广,直到无法增广,最终得到的就是最大流,同时也是最小割。

算法的正确性可以通过最大流最小割定理直接得出。算法的复杂度分析在此略去。

Edmonds-Karp算法

简单来说,每次沿着最短路增广。也就是使用广度优先搜索增广。

算法的正确性是显然的。时间复杂度为O(VE2)O(VE^2),不过我感觉这个上界很松。

关于时间复杂度的分析这里暂时略过,以后再填坑。

EK算法虽非最优,但已足以对付绝大多数情况。

Dinic算法

比EK算法多了一点点东西,并不很复杂。算法分成两个阶段:

  1. BFS分层:一个结点的层树是其到ss的最短距离
  2. DFS增广。增广有条件,每次只找比当前结点高度函数多1的结点进行增广,确保增广路最短

复杂度O(V2E)O(V^2E)

两个优化

  1. 多路增广:找到一条增广路之后,如果增广容量没有用尽,那么利用残余容量再找一条增广路
  2. 当前弧优化:如果一条边已经被增广过,那么就不必对其增广第二次。

Push-Relabel方法

  1. 预流:与普通的流相比,允许结点「存储」一定的流。如果对于一个结点,其流入的流比流出的流多,则称该结点溢出。(如果没有结点溢出,那么预流就是流。)

  2. 高度函数h(v)h(v)h:VNh: V \to \mathbb{N},满足

    1. h(s)=Vh(s) = \vert V\vert
    2. h(t)=0h(t) = 0
    3. (u,v)Ef,h(u)h(v)+1\forall(u,v)\in E_f, h(u) \le h(v) + 1

    高度还是就是push-relabel方法需要一直维护的量。

  3. 两个基本操作

    1. Push:如果结点uu溢出,则将超额流推送到uu的邻接结点(h(u)=h(v)+1h(u) = h(v) + 1c(u,v)f(u,v)>0c(u,v)-f(u,v)\gt 0
    2. Relabel:如果结点uu溢出,且h(u)h(v)h(u) \le h(v),则将h(u)h(u)更新为min(u,v)Eh(v)+1\displaystyle \min_{(u,v)\in E}h(v) + 1

Push-relabel方法正确性的证明,关键在于充分利用高度函数hh的性质。以及残存网络与流网络的关系。

通用预流推进算法

有了上面的准备,便可以引入push-relabel方法(通用预流推进算法)

  1. 初始化 f(u,v)={c(u,v)u=s0us f(u,v) = \begin{cases}c(u, v) & u = s\\ 0 & u\ne s\end{cases}

    h(u)={Vu=s0us h(u) = \begin{cases}\vert V\vert & u = s \\ 0 & u \ne s\end{cases}

  2. 选择结点进行Push和Relabel,直到所有结点都不溢出为止。

HLPP算法(最高标号预流推进算法)

  1. 初始化
  2. 选择溢出结点高度最高的结点,并对其进行Push
  3. 如果仍然溢出,则relabel,回到步骤2
  4. 如果没有溢出结点,算法结束

复杂度O(V2E)O(V^2\sqrt{E})

两个优化

  1. BFS优化:初始化h(u)h(u)uutt的最短距离
  2. GAP优化:如果h(u)=th(u)=t的结点个数为0,那么h(u)<th(u) \lt t的结点永远无法推送超额流到tt。因此,就将超额流送回ss。(将高度变成n+1n+1,以尽快推回ss