残量网络(residual network)中任意寻找一条从  s  到  t  的路径,然后增广,直到不存在这样的路径为止。这就是一般增广路算法(labeling algorithm)。可以证明这种不加改进的贪婪算法是正确的。假设最大流是  f  ,那么它的运行时间为  O( f⋅∣E∣) 。但是,这个运行时间并不好,因为它和最大流  f 

最短增广路增广,则运行时间可以减为  O(∣E∣2⋅∣V∣)  。这就是最短增广路算法。而 ISAP 算法则是最短增广路算法的一个改进。其实,ISAP 的意思正是「改进的最短增广路」 (Improved Shortest Augmenting Path)。

增广路方法(Ford-Fulkerson method)。和它对应的就是大名鼎鼎的预流推进方法(Preflow-push method)。其中最高标号预流推进算法(Highest-label preflow-push algorithm)的复杂度可以达到  O(∣V∣2∣E∣−−−−√)

算法解释

 s, t  不连通,算法结束。找最短路本质上就是无权最短路径问题,因此采用 BFS 的思想。具体来说,使用一个数组  d  ,记录每个节点到汇点  t  的最短距离。搜索的时候,只沿着满足  d[u]=d[v]+1  的边  u→v  (这样的边称为允许弧)走。显然,这样走出来的一定是最短路。

允许弧应该是属于残量网络的,而非原图的。换句话说,我们沿着允许弧,走的是残量网络(而非原图)中的最短路径。当我们找到沿着残量网络找到一条增广路,增广后,残量网络肯定会变化(至少少了一条边),因此决定允许弧的  d  数组要进行相应的更新(顺便提一句,Dinic 的做法就是每次增广都重新计算  d  数组)。然而,ISAP 「改进」的地方之一就是,其实没有必要马上更新  d 


 d  数组。那么怎么更新呢?非常简单:假设是从节点  u  找遍了邻接边也没找到允许弧的;再设一变量  m  ,令  m  等于残量网络中  u  的所有邻接点的  d  数组的最小值,然后令  d[u]  等于  m+1  即可。这是因为,进入 retreat 环节说明残量网络中  u  和  t  已经不能通过(已过时)的允许弧相连,那么  u  和  t  实际上在残量网络中的最短路的长是多少呢?(这正是  d  的定义!)显然是残量网络中  u  的所有邻接点和  t  的距离加  1  的最小情况。特殊情况是,残量网络中  u  根本没有邻接点。如果是这样,只需要把  d[u]  设为一个比较大的数即可,这会导致任何点到  u  的边被排除到残量网络以外。(严格来说只要大于等于  ∣V∣  即可。由于最短路一定是无环的,因此任意路径长最大是  ∣V∣−1  )。修改之后,只需要把正在研究的节点  u 

讲到这里,ISAP 算法的框架内容就讲完了。对于代码本身,还有几个优化和实现的技巧需要说明。

  1. 算法执行之前需要用 BFS 初始化  d  数组,方法是从  t  到  s 
  2. 算法主体需要维护一个「当前节点」  u 
  3. 记录路径的方法非常简单,声明一个数组  p  ,令  p[i]  等于增广路上到达节点  i  的边的序号(这样就可以找到从哪个顶点到的顶点  i 
  4. 判断残量网络中  s, t  不连通的条件,就是  d[s]≥∣V∣ 。这是因为当  s, t  不连通时,最终残量网络中  s  将没有任何邻接点,对  s 
  5. GAP 优化。GAP 优化可以提前结束程序,很多时候提速非常明显(高达 100 倍以上)。GAP 优化是说,进入 retreat 环节后,  u, t  之间的连通性消失,但如果  u  是最后一个和  t  距离  d[u]  (更新前)的点,说明此时  s, t  也不连通了。这是因为,虽然  u, t  已经不连通,但毕竟我们走的是最短路,其他点此时到  t  的距离一定大于  d[u]  (更新前),因此其他点要到  t  ,必然要经过一个和  t  距离为  d[u] 
  6. 另一个优化,就是用一个数组保存一个点已经尝试过了哪个邻接边。寻找增广的过程实际上类似于一个 BFS 过程,因此之前处理过的邻接边是不需要重新处理的(残量网络中的边只会越来越少)。具体实现方法直接看代码就可以,非常容易理解。需要注意的一点是,下次应该从上次处理到的邻接边继续处理,而非从上次处理到的邻接边的下一条开始。

 t  )后,沿着你记录的路径走一遍,记录一路上的最小残量,然后从  s  到  t 

实现

int source;         // 源点
int sink;           // 汇点
int p[max_nodes];   // 可增广路上的上一条弧的编号
int num[max_nodes]; // 和 t 的最短距离等于 i 的节点数量
int cur[max_nodes]; // 当前弧下标
int d[max_nodes];   // 残量网络中节点 i 到汇点 t 的最短距离
bool visited[max_nodes];

// 预处理, 反向 BFS 构造 d 数组
bool bfs()
{
    memset(visited, 0, sizeof(visited));
    queue<int> Q;
    Q.push(sink);
    visited[sink] = 1;
    d[sink] = 0;
    while (!Q.empty()) {
        int u = Q.front();
        Q.pop();
        for (iterator_t ix = G[u].begin(); ix != G[u].end(); ++ix) {
            Edge &e = edges[(*ix)^1];
            if (!visited[e.from] && e.capacity > e.flow) {
                visited[e.from] = true;
                d[e.from] = d[u] + 1;
                Q.push(e.from);
            }
        }
    }
    return visited[source];
}

// 增广
int augment()
{
    int u = sink, df = __inf;
    // 从汇点到源点通过 p 追踪增广路径, df 为一路上最小的残量
    while (u != source) {
        Edge &e = edges[p[u]];
        df = min(df, e.capacity - e.flow);
        u = edges[p[u]].from;
    }
    u = sink;
    // 从汇点到源点更新流量
    while (u != source) {
        edges[p[u]].flow += df;
        edges[p[u]^1].flow -= df;
        u = edges[p[u]].from;
    }
    return df;
}

int max_flow()
{
    int flow = 0;
    bfs();
    memset(num, 0, sizeof(num));
    for (int i = 0; i < num_nodes; i++) num[d[i]]++;
    int u = source;
    memset(cur, 0, sizeof(cur));
    while (d[source] < num_nodes) {
        if (u == sink) {
            flow += augment();
            u = source;
        }
        bool advanced = false;
        for (int i = cur[u]; i < G[u].size(); i++) { 
            Edge& e = edges[G[u][i]];
            if (e.capacity > e.flow && d[u] == d[e.to] + 1) {
                advanced = true;
                p[e.to] = G[u][i];
                cur[u] = i;
                u = e.to;
                break;
            }
        }
        if (!advanced) { // retreat
            int m = num_nodes - 1;
            for (iterator_t ix = G[u].begin(); ix != G[u].end(); ++ix)
                if (edges[*ix].capacity > edges[*ix].flow)
                    m = min(m, d[edges[*ix].to]);
            if (--num[d[u]] == 0) break; // gap 优化
            num[d[u] = m+1]++;
            cur[u] = 0;
            if (u != source)
                u = edges[p[u]].from;
        }
    }
    return flow;
}