Max Flow

This page mainly introduces algorithms related to the maximum flow problem.

Overview

For the basic concepts of network flow, see Introduction to Network Flow.

Let G=(V,E)G=(V,E) be a network with a source and a sink. We want to specify a suitable flow on GG, denoted ff, to maximize the flow of the entire network ∣f∣|f| (that is, ∑x∈Vf(s,x)−∑x∈Vf(x,s)\sum_{x \in V} f(s, x) - \sum_{x \in V} f(x, s)). This is called the maximum flow problem.

Ford–Fulkerson Augmentation

Ford–Fulkerson augmentation is a general name for a class of algorithms that compute maximum flow. It uses a greedy approach: find augmenting paths and update the maximum flow.

Overview

Given GG and a flow on GG, denoted ff, we make the following definitions.

For an edge (u,v)(u, v), we call the difference between its capacity and flow the residual capacity cf(u,v)c_f(u,v) (Residual Capacity), namely cf(u,v)=c(u,v)−f(u,v)c_f(u,v)=c(u,v)-f(u,v).

The subgraph consisting of all vertices in GG and all edges whose residual capacity is greater than 00 is called the residual network GfG_f (Residual Network), namely Gf=(V,Ef)G_f=(V,E_f), where Ef={(u,v)∣cf(u,v)>0}E_f=\left\{(u,v) \mid c_f(u,v)>0\right\}.

Warning

As we will soon mention, flow can be negative, so edges of EfE_f may not be present in EE. The introduction of augmentation will explain this in detail below.

In GfG_f, a path from the source ss to the sink tt is called an augmenting path (Augmenting Path). For an augmenting path, we add the same amount of flow to every edge (u,v)(u, v), increasing the flow of the entire network. This process is called augmentation (Augment). Thus, finding a maximum flow can be viewed as superimposing the flows obtained from several augmentations.

In addition, during Ford–Fulkerson augmentation, for every edge (u,v)(u, v) we create a reverse edge (v,u)(v, u). We stipulate that f(u,v)=−f(v,u)f(u, v) = -f(v, u). This property can be maintained by introducing flow cancellation during each augmentation: when f(u,v)f(u, v) increases, f(v,u)f(v, u) should decrease by the same amount.

Tip

In implementations of maximum flow algorithms, we often need to access reverse edges quickly. In an adjacency matrix, this is trivial (gu,v↔gv,ug_{u, v} \leftrightarrow g_{v, u}). However, the mainstream implementation is the more efficient chain forward-star representation. A common technique is to number edges starting from an even number (usually 00), and always add the reverse edge immediately after adding an edge, so their numbers are adjacent. Thus, edge ii and edge i⊕1i \oplus 1 are always reverse edges of each other.

Readers encountering this method for the first time may notice a counterintuitive situation: the flow on a reverse edge f(v,u)f(v, u) may be negative. In fact, during Ford–Fulkerson augmentation, the meaningful quantity is the residual capacity cfc_f, and the absolute value of f(v,u)f(v, u) is irrelevant. We can regard decreasing the flow on the reverse edge as increasing its residual capacity cf(v,u)c_f(v, u)—which is also consistent with the meaning of flow cancellation. Increasing the residual capacity of a reverse edge means that we may next traverse the reverse edge to cancel a previous forward augmentation, representing an operation of “regret”.

The following example may help you understand this process. Suppose GG is a unit-capacity network, and consider the following process:

  • There are multiple augmenting paths in GG. We choose to perform an augmentation that passes through u,vu, v in that order (as shown in the left figure), increasing the flow by 11.
  • Notice that if we performed the augmentation in the middle figure, the local maximum flow would be not 11 but 22. However, because the edge entering uu and the edge leaving vv exhausted their capacities in the first augmentation, we cannot perform the middle augmentation now. This means our current flow is not optimal, but there may be no other augmenting path locally that uses only edges from the original graph and no reverse edges.
  • Now introduce flow cancellation. After the first augmentation, cancellation increases the residual capacity cf(v,u)c_f(v, u) by 11, which is equivalent to adding a new edge (v,u)(v, u). We can therefore perform another augmentation passing through p,v,u,qp, v, u, q in that order (the orange path in the right figure). The flows on the undirected edge (u,v)(u, v) cancel across the two augmentations, and we are surprised to find that the combined result is actually equivalent to the middle figure.
Figure: flow2

The example above shows that the “cancellation” effect of flow cancellation means we need not worry that we chose augmenting paths in the “wrong” order.

It is easy to see that as long as an augmenting path exists in GfG_f, augmenting along it increases the total flow; otherwise, the total flow has reached its maximum possible value and the computation is complete. This is the Ford–Fulkerson augmentation process.

Max-Flow Min-Cut Theorem

We have a general understanding of Ford–Fulkerson augmentation, but how can we prove that it is correct? Why is the flow ff after augmentation ends a maximum flow?

In fact, the correctness of Ford–Fulkerson augmentation is equivalent to the max-flow min-cut theorem (The Maxflow-Mincut Theorem). This theorem states that for any network G=(V,E)G = (V, E), its maximum flow ff and minimum cut {S,T}\{S, T\} always satisfy ∣f∣=∣∣S,T∣∣|f| = ||S, T||.

To prove the max-flow min-cut theorem, we first begin with a lemma: for a network G=(V,E)G = (V, E), any flow ff and cut {S,T}\{S, T\} always satisfy ∣f∣≤∣∣S,T∣∣|f| \leq ||S, T||. Equality holds if and only if all edges in {(u,v)∣u∈S,v∈T}\{(u, v) | u \in S, v \in T\} are saturated and all edges in {(u,v)∣u∈T,v∈S}\{(u, v) | u \in T, v \in S\} carry zero flow.

Proof ∣f∣=f(s)=∑u∈Sf(u)=∑u∈S(∑v∈Vf(u,v)−∑v∈Vf(v,u))=∑u∈S(∑v∈Tf(u,v)+∑v∈Sf(u,v)−∑v∈Tf(v,u)−∑v∈Sf(v,u))=∑u∈S(∑v∈Tf(u,v)−∑v∈Tf(v,u))+∑u∈S∑v∈Sf(u,v)−∑u∈S∑v∈Sf(v,u)=∑u∈S(∑v∈Tf(u,v)−∑v∈Tf(v,u))≤∑u∈S∑v∈Tf(u,v)≤∑u∈S∑v∈Tc(u,v)=∣∣S,T∣∣\begin{aligned} |f| & = f(s) \\ & = \sum_{u \in S} f(u) \\ & = \sum_{u \in S} \left( \sum_{v \in V} f(u, v) - \sum_{v \in V} f(v, u) \right) \\ & = \sum_{u \in S} \left( \sum_{v \in T} f(u, v) + \sum_{v \in S} f(u, v) - \sum_{v \in T} f(v, u) - \sum_{v \in S} f(v, u) \right) \\ & = \sum_{u \in S} \left( \sum_{v \in T} f(u, v) - \sum_{v \in T} f(v, u) \right) + \sum_{u \in S} \sum_{v \in S} f(u, v) - \sum_{u \in S} \sum_{v \in S} f(v, u) \\ & = \sum_{u \in S} \left( \sum_{v \in T} f(u, v) - \sum_{v \in T} f(v, u) \right) \\ & \leq \sum_{u \in S} \sum_{v \in T} f(u, v) \\ & \leq \sum_{u \in S} \sum_{v \in T} c(u, v) \\ & = ||S, T|| \\ \end{aligned}

For equality, the first inequality requires all edges in {(u,v)∣u∈T,v∈S}\{(u, v) \mid u \in T, v \in S\} to carry zero flow, and the second requires all edges in {(u,v)∣u∈S,v∈T}\{(u, v) \mid u \in S, v \in T\} to be saturated. The original lemma follows.

For any network, can the equality conditions above always be satisfied? If the answer is yes, the max-flow min-cut theorem follows. We now prove this.

Proof

Suppose that after one augmentation we obtain a flow ff such that no augmenting path exists in GfG_f, that is, in GfG_f there is no path from ss to tt. Let the vertices reachable from ss form the set SS, and let T=V∖ST = V \setminus S.

Clearly, {S,T}\{S, T\} is a cut of GfG_f, and ∣∣S,T∣∣=∑u∈S∑v∈Tcf(u,v)=0||S, T|| = \sum_{u \in S} \sum_{v \in T} c_f(u, v) = 0. Since residual capacities are nonnegative, this also means that for any u∈S,v∈T,(u,v)∈Efu \in S, v \in T, (u, v) \in E_f, we have cf(u,v)=0c_f(u, v) = 0. We now discuss these edges in two cases, according to whether they exist in the original graph or are reverse edges:

  • (u,v)∈E(u, v) \in E: in this case, cf(u,v)=c(u,v)−f(u,v)=0c_f(u, v) = c(u, v) - f(u, v) = 0, so c(u,v)=f(u,v)c(u, v) = f(u, v); all edges in {(u,v)∣u∈S,v∈T}\{(u, v) \mid u \in S, v \in T\} are saturated;
  • (v,u)∈E(v, u) \in E: in this case, cf(u,v)=c(u,v)−f(u,v)=0−f(u,v)=f(v,u)=0c_f(u, v) = c(u, v) - f(u, v) = 0 - f(u, v) = f(v, u) = 0; all edges in {(v,u)∣u∈S,v∈T}\{(v, u) \mid u \in S, v \in T\} carry zero flow.

Therefore, after augmentation stops, the flow ff satisfies the equality conditions above. By the size relation stated in the lemma, ff is naturally a maximum flow of GG, and {S,T}\{S, T\} is a minimum cut of GG.

It is easy to see that König's theorem is a special case of the max-flow min-cut theorem. In fact, both are related to duality in linear programming.

Time Complexity Analysis

For a network G=(V,E)G = (V, E) with integer flows, assume trivially that each augmentation has an integer flow. Then an upper bound on the time complexity of Ford–Fulkerson augmentation is O(∣E∣∣f∣)O(|E||f|), where ff is the maximum flow on GG. A single augmentation takes O(∣E∣)O(|E|) time, and each augmentation increases the total flow, so the number of augmentations cannot exceed ∣f∣|f|.

Different implementations of Ford–Fulkerson augmentation have different time complexities. The more mainstream implementations include Edmonds–Karp, Dinic, SAP, and ISAP, which we introduce separately below.

Edmonds–Karp Algorithm

Algorithm Idea

How do we find an augmenting path in GfG_f? When considering a concrete implementation of Ford–Fulkerson augmentation, the most natural approach is to use BFS. Ford–Fulkerson augmentation then becomes the Edmonds–Karp algorithm. Its process is as follows:

  • In GfG_f, if we can BFS from ss to tt, we have found a new augmenting path.

  • For an augmenting path pp, compute the minimum residual capacity of the edges on pp, Δ=min⁡(u,v)∈pcf(u,v)\Delta = \min_{(u, v) \in p} c_f(u, v). Add flow to every edge on pp in an amount of Δ\Delta, and cancel Δ\Delta flow on each reverse edge, increasing the maximum flow by Δ\Delta.

  • Because the flow has changed, we obtain a new GfG_f. Repeat the process above on the new GfG_f until no augmenting path exists, at which point the flow no longer increases.

The algorithm above is the Edmonds–Karp algorithm.

Time Complexity Analysis

Next, let us analyze the time complexity of the Edmonds–Karp algorithm.

Clearly, a single BFS augmentation takes O(∣E∣)O(|E|) time.

The upper bound on the total number of augmentations is O(∣V∣∣E∣)O(|V||E|). This claim is often falsely proved (or skipped with vague wording) in network-flow materials. We now give a more formal proof1.

Proof of the Upper Bound on the Total Number of Augmentations

First, we introduce a lemma—the shortest-path nondecreasing lemma. Let df(u)d_f(u) denote the distance in GfG_f from vertex uu to the source ss (that is, the shortest-path length; the same convention applies below). For one augmentation, let ff and f′f' denote the flow before and after augmentation, respectively. We claim that for every vertex uu, augmentation always gives df′(u)≥df(u)d_{f'}(u) \geq d_f(u). We prove this lemma shortly.

Call an edge with minimum residual capacity on an augmenting path a saturated edge (if several edges attain the minimum, choose any one). If a directed edge (u,v)(u, v) is selected as the saturated edge, augmentation empties its residual capacity, causing the saturated edge to disappear, while flow cancellation adds its reverse edge (if the reverse edge did not previously exist), namely (u,v)∉Ef′(u, v) \not \in E_{f'} and (v,u)∈Ef′(v, u) \in E_{f'}. This analysis shows that for an undirected edge (u,v)(u, v), when augmenting in GfG_f along (u,v)(u, v), df(u)+1=df(v)d_f(u) + 1 = d_f(v). The residual network then becomes Gf′G_{f'}. In Gf′G_{f'}, augment along (v,u)(v, u), df′(v)+1=df′(u)d_{f'}(v) + 1 = d_{f'}(u). By the shortest-path nondecreasing lemma, df′(v)≥df(v)d_{f'}(v) \geq d_f(v). Combining these equations gives df′(u)≥df(u)+2d_{f'}(u) \geq d_{f}(u) + 2. In other words, if directed edge (u,v)(u, v) is selected as a saturated edge, then compared with the previous time it was selected, the distance from uu to ss increases by at least 22.

The distance from ss to any vertex cannot exceed ∣V∣|V|. Together with the property above, this shows that each edge is selected as a saturated edge O(∣V∣)O(|V|) times. Multiplying by the number of edges gives the upper bound O(∣V∣∣E∣)O(|V||E|) on the total number of augmentations.

We now prove the shortest-path nondecreasing lemma, namely df′(u)≥df(u)d_{f'}(u) \geq d_f(u). The proof is not difficult, but may be somewhat circuitous, so readers may wish to pause and think carefully.

Proof of the Shortest-Path Nondecreasing Lemma

Consider a contradiction. For one augmentation, suppose that some vertices have a smaller distance to ss after the augmentation than before it. Let vv be the one among them with the smallest distance to ss (that is, v=arg⁡min⁡x∈V,df′(x)<df(x)df′(x)v = \arg \min_{x \in V, d_{f'}(x) < d_f(x)} d_{f'}(x)). By the contradiction assumption, df′(v)<df(v)d_{f'}(v) < d_f(v) is known.

On the shortest path in Gf′G_{f'} from ss to vv, let uu be the predecessor of vv, so df′(u)+1=df′(v)d_{f'}(u) + 1 = d_{f'}(v).

To ensure that uu does not violate the property that vv has the smallest distance, uu must satisfy df′(u)≥df(u)d_{f'}(u) \geq d_f(u).

Adding the two sides of the previous inequality gives df′(v)≥df(u)+1d_{f'}(v) \geq d_f(u) + 1. Applying the contradiction assumption, we obtain df(v)>df(u)+1d_f(v) > d_f(u) + 1.

We now discuss the augmentation direction on (u,v)(u, v).

  • Suppose (u,v)∈Ef(u, v) \in E_f. By the BFS property, we have df(u)+1≥df(v)d_f(u) + 1 \geq d_f(v). This conflicts with the inequality above and yields a contradiction.
  • Suppose (u,v)∉Ef(u, v) \not \in E_f. By the definition of uu, we know (u,v)∈Ef′(u, v) \in E_{f'}, so this edge must have been created when the current augmentation traversed (v,u)(v, u) and generated a reverse edge through cancellation; that is, df(v)+1=df(u)d_f(v) + 1 = d_f(u). This conflicts with the inequality above and yields a contradiction.

Since augmenting along (u,v)(u, v) in either direction yields a contradiction, the contradiction assumption is false, and the shortest-path nondecreasing lemma follows.

Multiplying the time complexity of a single BFS augmentation by the upper bound on the number of augmentations, we obtain the Edmonds–Karp time complexity O(∣V∣∣E∣2)O(|V||E|^2).

Implementation

A possible implementation of the Edmonds–Karp algorithm is as follows.

Reference code
constexpr int MAXN = 250;
constexpr int INF = 0x3f3f3f3f;

struct Edge {
  int from, to, cap, flow;

  Edge(int u, int v, int c, int f) : from(u), to(v), cap(c), flow(f) {}
};

struct EK {
  int n, m;             // n: number of vertices, m: number of edges
  vector<Edge> edges;   // edges: collection of all edges
  vector<int> G[MAXN];  // G: indices in edges of all edges starting at vertex x
  int a[MAXN], p[MAXN];  // a: maximum flow assigned to x by the edge that first reaches x during BFS
                         // p: edge that first reaches x during BFS

  void init(int n) {
    for (int i = 0; i < n; i++) G[i].clear();
    edges.clear();
  }

  void AddEdge(int from, int to, int cap) {
    edges.push_back(Edge(from, to, cap, 0));
    edges.push_back(Edge(to, from, 0, 0));
    m = edges.size();
    G[from].push_back(m - 2);
    G[to].push_back(m - 1);
  }

  int Maxflow(int s, int t) {
    int flow = 0;
    for (;;) {
      memset(a, 0, sizeof(a));
      queue<int> Q;
      Q.push(s);
      a[s] = INF;
      while (!Q.empty()) {
        int x = Q.front();
        Q.pop();
        for (int i = 0; i < G[x].size(); i++) {  // Traverse edges starting at x
          Edge& e = edges[G[x][i]];
          if (!a[e.to] && e.cap > e.flow) {
            p[e.to] = G[x][i];  // G[x][i] is the edge that first reaches e.to
            a[e.to] =
                min(a[x], e.cap - e.flow);  // Assign the flow of the edge that first reaches e.to
            Q.push(e.to);
          }
        }
        if (a[t]) break;  // If the sink has received flow, exit BFS
      }
      if (!a[t])
        break;  // If the sink received no flow, the source and sink are disconnected
      for (int u = t; u != s;
           u = edges[p[u]].from) {  // Trace the s -> t path found by BFS through u
        edges[p[u]].flow += a[t];      // Increase the flow on the path edge
        edges[p[u] ^ 1].flow -= a[t];  // Decrease the flow on the reverse edge
      }
      flow += a[t];
    }
    return flow;
  }
};

Dinic Algorithm

Algorithm Idea

Before augmentation, perform BFS on GfG_f to build layers, dividing vertices according to the distance from each vertex uu to the source ss, denoted d(u)d(u). Require flow passing through uu to move only to a vertex vv in the next layer; that is, remove outgoing edges from uu to vertices with equal or smaller layer labels. We call the remaining part of GfG_f the level graph (Level Graph). Formally, GL=(V,EL)G_L = (V, E_L) is the level graph of Gf=(V,Ef)G_f = (V, E_f), where EL={(u,v)∣(u,v)∈Ef,d(u)+1=d(v)}E_L = \left\{ (u, v) \mid (u, v) \in E_f, d(u) + 1 = d(v) \right\}.

On GLG_L, if we find a maximal augmenting flow fbf_b such that using only GLG_L it is impossible to increase fbf_b, then we call fbf_b a blocking flow of GLG_L.

Warning

Although above we defined augmentation/augmenting flow only on a single augmenting path, in a broader sense the word “augmentation” can apply not only to the augmenting flow on a single path but also to the union of several augmenting flows—the latter is the sense used in our definition of a blocking flow.

After defining the level graph and blocking flow, the Dinic algorithm proceeds as follows.

  1. BFS on GfG_f to construct the level graph GLG_L.
  2. DFS on GLG_L to find a blocking flow fbf_b.
  3. Merge fbf_b into the original flow ff, namely f←f+fbf \leftarrow f + f_b.
  4. Repeat the above process until no path from ss to tt exists.

The resulting ff is the maximum flow.

Before analyzing this algorithm's complexity, we need to clarify the process of “using DFS on GLG_L to find a blocking flow fbf_b”. Although BFS layering should be trivial for readers of this page, DFS for a blocking flow requires some technique—we need to introduce current-arc optimization.

During DFS on GLG_L, if vertex uu has many incoming and outgoing edges and, whenever uu receives flow from an incoming edge, it scans the outgoing-edge list to decide which outgoing edge should receive the flow, the local worst-case time complexity at uu can reach O(∣E∣2)O(|E|^2). To avoid this, once we know that edge (u,v)(u, v) has been augmented to its limit (edge (u,v)(u, v) has no residual capacity or the portion after vv has already become blocked), there is no need for uu to try sending flow through outgoing edge (u,v)(u, v) again. Accordingly, for each vertex uu, we maintain the first outgoing edge in the outgoing-edge list of uu that still needs to be tried. This pointer is conventionally called the current arc, and the technique is called current-arc optimization.

Multi-Path Augmentation

Multi-path augmentation is a constant-factor optimization of the Dinic algorithm. If we find a path from ss to tt in the level graph and call it pp, we need not necessarily start from ss again to find the next augmenting path; instead, we may start from the last position on pp that still has residual capacity and search for a branch for another augmentation. Because this is consistent with the backtracking form, the optimization arises naturally in the DFS implementation.

Common Misconception

Perhaps because of the spread of incorrect descriptions in many online resources, a considerable number of contestants like to call current-arc optimization and multi-path augmentation the two optimizations of the Dinic algorithm. In fact, current-arc optimization is part of what ensures the correctness of Dinic's time complexity, whereas multi-path augmentation is merely a constant-factor optimization that does not affect the complexity.

Time Complexity Analysis

After applying current-arc optimization, the time complexity of the Dinic algorithm can be analyzed as follows.

First, we prove that the time complexity of using DFS to find a blocking flow in a single augmentation phase is O(∣V∣∣E∣)O(|V||E|).

Proof of the Time Complexity of a Single Augmentation Phase

Consider every augmenting path in the blocking flow fbf_b. Each is obtained on GLG_L by repeatedly following the current arc, and the number of jumps along each augmenting path is at most ∣V∣|V|.

Each time an augmenting path is found, one saturated edge disappears (its residual capacity becomes zero). For every augmenting path in fbf_b, let E1E_1 be the set of saturated edges whose residual capacities it clears. Because of the layering property of GLG_L, after a saturated edge disappears, its reverse edge cannot be traversed by another augmenting path in the same augmentation phase. Therefore, E1E_1 is a subset of ELE_L.

In addition, for cases where following current arcs is blocked at some position and therefore does not successfully produce an augmenting path, let E2E_2 be the set of final edges on these incomplete paths. Members of E2E_2 are not saturated, so E1E_1 and E2E_2 are disjoint, and E1∪E2E_1 \cup E_2 is still a subset of ELE_L.

Every member of E1∪E2E_1 \cup E_2 is involved in at most ∣V∣|V| jumps (and some jumps are counted repeatedly after multi-path augmentation optimization). Therefore, the total number of jumps in the DFS process is at most ∣V∣∣EL∣|V||E_L|.

A Common False Proof

For each vertex, we maintain the next edge that can be augmented, and the current arc changes at most ∣E∣|E| times, so the worst-case time complexity of a single augmentation phase is O(∣V∣∣E∣)O(|V||E|).

Bug

“The current arc changes at most ∣E∣|E| times” does not imply that “each vertex visits its outgoing edges at most ∣E∣|E| times”. The reason is that visiting the current arc does not necessarily exhaust its residual capacity; vertex uu may visit the same current arc multiple times.

The number of layers in a level graph obviously cannot exceed ∣V∣|V|. If we can prove that the number of layers strictly increases during augmentation, then the number of augmentation phases of Dinic's algorithm is O(∣V∣)O(|V|). We now prove this conclusion2.

Proof of the Monotonicity of the Number of Layers

We introduce a concept from preflow-push algorithms (another class of maximum-flow algorithms)—height labels. To state the proof more conveniently with height labels, let df(u)d_f(u) denote the distance in GfG_f from vertex uu to the sink tt, and build layers from the sink rather than the source (there is no essential difference). For one augmentation phase, let ff and f′f' be the flows before and after augmentation. After finding and adding the blocking flow in this phase, let the level graph change from GL=(V,EL)G_L = (V, E_L) to GL′=(V,EL′)G'_{L} = (V, E'_L).

Temporarily, and somewhat informally, define a height label as follows: on a network G=(V,E)G = (V, E), let hh be a function from VV to the integers NN; hh is a valid height labeling on GG if and only if h(u)≤h(v)+1h(u) \leq h(v) + 1 holds for every (u,v)∈E(u, v) \in E.

Consider Ef′E_{f'}, and for each member (u,v)(u, v), we find that (u,v)∈Ef′(u, v) \in E_{f'} for one of two reasons:

  • (u,v)∈Ef(u, v) \in E_f, and its residual capacity was not exhausted during this augmentation phase. By the definition of shortest paths, df(u)≤df(v)+1d_f(u) \leq d_f(v) + 1;
  • (u,v)∉Ef(u, v) \not \in E_f, but the blocking flow traversed (v,u)(v, u) during this augmentation phase and generated the reverse edge through cancellation. By the definition of the level graph and blocking flow, df(u)+1=df(v)d_f(u) + 1 = d_f(v).

These observations show that dfd_f is a valid height labeling on Gf′G_{f'}, and of course also on Gf′G_{f'} and its subgraph GL′G'_L, namely the level graph GL′G'_L.

Now consider an augmenting path p=(s,…,u,v,…,t)p = (s, \dots, u, v, \dots, t). Starting from the empty path, consider adding vertices one at a time in reverse order along pp, from tt to ss. Suppose vv has already been added and uu is being added. After adding uu, by the definition of the level graph, the value of df′(u)d_{f'}(u) is greater than df′(v)d_{f'}(v) by 11. Meanwhile, because dfd_f is a height labeling on GL′G'_L, df(u)d_f(u) may be greater than df(v)d_f(v) by 11, equal to it, or smaller. Therefore, after adding the entire path, we obtain df′(s)≥df(s)d_{f'}(s) \geq d_f(s), with equality if and only if df(u)=df(v)+1d_f(u) = d_f(v) + 1 for every (u,v)∈p(u, v) \in p. If equality does not hold, then df′(s)>df(s)d_{f'}(s) > d_f(s)—the desired conclusion that the number of layers strictly increases during augmentation. We now prove that equality cannot hold.

Suppose for a contradiction that df′(s)=df(s)d_{f'}(s) = d_f(s). We claim that in GL′G'_L, the path pp contains at least one edge (u,v)(u, v); this edge (u,v)(u, v) does not exist in GLG_L. If no such edge existed, then because df(s)=df′(s)d_f(s) = d_{f'}(s), the definitions of the level graph and blocking flow would imply that augmentation in GLG_L was not complete. To avoid this contradiction, our claim must be true.

Let (u,v)(u, v) be an edge satisfying the claim. There are only two possible reasons:

  • (u,v)∈Ef(u, v) \in E_f, but df(u)≤df(v)+1d_f(u) \leq d_f(v) + 1 is strict, so by the definition of the level graph, (u,v)∉EL(u, v) \not \in E_L and is added to EL′E'_L when the graph is re-layered after augmentation;
  • (u,v)∉Ef(u, v) \not \in E_f, which means that edge (u,v)(u, v) was created when the blocking flow traversed (v,u)(v, u) during the current augmentation phase and generated a reverse edge through cancellation, that is, df(u)=df(v)−1d_f(u) = d_f(v) - 1.

In either case, we obtain df(u)≠df(v)+1d_f(u) \neq d_f(v) + 1. Thus the necessary and sufficient condition for equality in df′(s)≥df(s)d_{f'}(s) \geq d_f(s) cannot be satisfied, contradicting the assumption df′(s)=df(s)d_{f'}(s) = d_f(s). The proposition follows.

Another Common False Proof

Suppose for a contradiction that after an augmentation phase the level count is equal to its previous value. Then there should still be at least one augmenting path from ss to tt in the level graph with a layer difference of 11 between adjacent vertices. Since this path was not augmented, the augmentation phase has not finished. To avoid the contradiction, the original proposition follows.

[!error]- Bug

“The shortest ss-tt path in the new level graph has the same length as before the augmentation phase” does not imply that “the augmentation in the old level graph has not finished”. There is no reason that the two level graphs have the same edge set; the shortest ss-tt path in the new level graph may use an edge that did not exist in the old level graph.

Multiplying the single-phase time complexity O(∣V∣∣E∣)O(|V||E|) by the O(∣V∣)O(|V|) bound on the number of augmentation phases gives the Dinic time complexity O(∣V∣2∣E∣)O(|V|^2|E|).

To make the actual running time of Dinic close to its theoretical upper bound, we would need to construct an input network with special properties. In algorithm competitions, however, network-flow problems usually focus on modeling the original problem as a flow network. Such models generally do not contain the special properties that make Dinic slow; on the contrary, Dinic is highly efficient on most graphs. Therefore, network-flow problems usually have large data ranges, and estimating running time by substituting ∣V∣,∣E∣|V|, |E| into ∣V∣2∣E∣|V|^2|E| is not applicable. Accurate estimation requires contestants to have some experience with Dinic's practical efficiency, so readers should practice more.

Time Complexity Analysis for Special Cases

On some well-behaved graphs, Dinic has a better time complexity.

For a network G=(V,E)G = (V, E), if all edge capacities are 11, that is, c(u,v)∈{0,1}c(u, v) \in \{0, 1\} for every (u,v)∈E(u, v) \in E, then GG is called a unit-capacity network (Unit Capacity).

In a unit-capacity network, the time complexity of one augmentation phase of Dinic is O(∣E∣)O(|E|).

Proof

This is because every augmentation saturates and removes all edges on the augmenting path, so each edge can be augmented only once in a single augmentation phase.

In a unit-capacity network, the number of augmentation phases of Dinic is O(∣E∣12)O(|E|^{\frac{1}{2}}).

Proof

Build layers centered at the source ss, let df(u)d_f(u) denote the distance in GfG_f from vertex uu to the source ss. In addition, define the set {u∣u∈V,df(u)=k}\left\{u \mid u \in V, d_f(u) = k \right\} as the layer with index kk, denoted DkD_k, and let Sk=∪i≤kDiS_k = \cup_{i \leq k} D_i.

Suppose we have performed ∣E∣12|E|^{\frac{1}{2}} augmentation phases. By the pigeonhole principle, there is at least one kk such that the edge set {(u,v)∣u∈Dk,v∈Dk+1,(u,v)∈Ef}\left\{ (u, v) \mid u \in D_k, v \in D_{k+1}, (u, v) \in E_f \right\} has size at most ∣E∣∣E∣12≈∣E∣12\frac {|E|} {|E|^{\frac{1}{2}}} \approx |E|^{\frac{1}{2}}. Clearly, {Sk,V−Sk}\{S_k, V - S_k\} is a cut in GfG_f separating ss and tt, and its cut capacity is at most ∣E∣12|E|^{\frac{1}{2}}. By the max-flow min-cut theorem, the maximum flow in GfG_f is at most ∣E∣12|E|^{\frac{1}{2}}, meaning that in GfG_f at most ∣E∣12|E|^{\frac{1}{2}} more augmentation phases can be performed. Therefore, the total number of augmentation phases is O(∣E∣12)O(|E|^{\frac{1}{2}}).

In a unit-capacity network, the number of augmentation phases of Dinic is O(∣V∣23)O(|V|^{\frac{2}{3}}).

Proof

Suppose we have performed 2∣V∣232 |V|^{\frac{2}{3}} augmentation phases. Since at most half of the layers (∣V∣23|V|^{\frac{2}{3}} of them) can contain more than ∣V∣13|V|^{\frac{1}{3}} vertices, regardless of how the layer sizes are distributed, there is at least one kk such that two adjacent layers each contain at most ∣V∣13|V|^{\frac{1}{3}} vertices, namely ∣Dk∣≤∣V∣13|D_k| \leq |V|^{\frac{1}{3}} and ∣Dk+1∣≤∣V∣13|D_{k+1}| \leq |V|^{\frac{1}{3}}.

To maximize the number of edges between DkD_k and Dk+1D_{k+1}, assume they form a complete bipartite graph. Then the edge set {(u,v)∣u∈Dk,v∈Dk+1,(u,v)∈Ef}\left\{ (u, v) \mid u \in D_k, v \in D_{k+1}, (u, v) \in E_f \right\} has size at most ∣V∣23|V|^{\frac{2}{3}}. Clearly, {Sk,V−Sk}\{S_k, V - S_k\} is a cut in GfG_f separating ss and tt, and its cut capacity is at most ∣V∣23|V|^{\frac{2}{3}}. By the max-flow min-cut theorem, the maximum flow in GfG_f is at most ∣V∣23|V|^{\frac{2}{3}}, meaning that in GfG_f at most ∣V∣23|V|^{\frac{2}{3}} more augmentation phases can be performed. Therefore, the total number of augmentation phases is O(∣V∣23)O(|V|^{\frac{2}{3}}).

In a unit-capacity network, if every vertex uu other than the source and sink satisfies degin(u)=1\mathit{deg}_{\mathit{in}}(u) = 1 or degout(u)=1\mathit{deg}_{\mathit{out}}(u) = 1, then the number of augmentation phases of Dinic is O(∣V∣12)O(|V|^{\frac{1}{2}}). Here, degin(u)\mathit{deg}_{\mathit{in}}(u) and degout(u)\mathit{deg}_{\mathit{out}}(u) denote the in-degree and out-degree of vertex uu, respectively.

Proof

We introduce the following lemma: for a network of this form, any flow can always be decomposed into several unit-flow, vertex-disjoint augmenting paths.

Suppose we have performed ∣V∣12|V|^{\frac{1}{2}} augmentation phases. By the definition of the level graph, every new augmenting path now has length at least ∣V∣12|V|^{\frac{1}{2}}.

Consider a decomposition of the maximum flow into augmenting paths in GfG_f. The number of augmenting paths cannot exceed ∣V∣∣V∣12≈∣V∣12\frac {|V|} {|V|^{\frac{1}{2}}} \approx |V|^{\frac{1}{2}}, meaning that at most ∣V∣12|V|^{\frac{1}{2}} more augmentation phases can be performed in GfG_f. Therefore, the total number of augmentation phases is O(∣V∣12)O(|V|^{\frac{1}{2}}).

In summary, we obtain the following corollaries.

  • On a unit-capacity network, the total time complexity of Dinic is O(∣E∣min⁡(∣E∣12,∣V∣23))O(|E| \min(|E|^\frac{1}{2}, |V|^{\frac{2}{3}})).
  • On a unit-capacity network, if every vertex uu other than the source and sink satisfies degin(u)=1\mathit{deg}_{\mathit{in}}(u) = 1 or degout(u)=1\mathit{deg}_{\mathit{out}}(u) = 1, the total time complexity of Dinic is O(∣E∣∣V∣12)O(|E||V|^{\frac{1}{2}}). For maximum bipartite matching, we commonly use the Hopcroft–Karp algorithm, which is in fact a special case of Dinic on a unit-capacity network satisfying the degree restrictions above.

Implementation

Reference code
struct MF {
  struct edge {
    int v, nxt, cap, flow;
  } e[N];

  int fir[N], cnt = 0;

  int n, S, T;
  ll maxflow = 0;
  int dep[N], cur[N];

  void init() {
    memset(fir, -1, sizeof fir);
    cnt = 0;
  }

  void addedge(int u, int v, int w) {
    e[cnt] = {v, fir[u], w, 0};
    fir[u] = cnt++;
    e[cnt] = {u, fir[v], 0, 0};
    fir[v] = cnt++;
  }

  bool bfs() {
    queue<int> q;
    memset(dep, 0, sizeof(int) * (n + 1));

    dep[S] = 1;
    q.push(S);
    while (q.size()) {
      int u = q.front();
      q.pop();
      for (int i = fir[u]; ~i; i = e[i].nxt) {
        int v = e[i].v;
        if ((!dep[v]) && (e[i].cap > e[i].flow)) {
          dep[v] = dep[u] + 1;
          q.push(v);
        }
      }
    }
    return dep[T];
  }

  int dfs(int u, int flow) {
    if ((u == T) || (!flow)) return flow;

    int ret = 0;
    for (int& i = cur[u]; ~i; i = e[i].nxt) {
      int v = e[i].v, d;
      if ((dep[v] == dep[u] + 1) &&
          (d = dfs(v, min(flow - ret, e[i].cap - e[i].flow)))) {
        ret += d;
        e[i].flow += d;
        e[i ^ 1].flow -= d;
        if (ret == flow) return ret;
      }
    }
    return ret;
  }

  void dinic() {
    while (bfs()) {
      memcpy(cur, fir, sizeof(int) * (n + 1));
      maxflow += dfs(S, INF);
    }
  }
} mf;

MPM Algorithm

The MPM (Malhotra, Pramodh-Kumar and Maheshwari) algorithm obtains maximum flow in two ways: a heap-based priority queue with time complexity O(n3log⁡n)O(n^3\log n), or the commonly used BFS solution with time complexity O(n3)O(n^3). This section focuses only on the better and simpler O(n3)O(n^3) algorithm.

The overall structure of the MPM algorithm is similar to Dinic: it runs in phases, and in each phase finds augmenting paths in the layered residual network of GG. Its main difference from Dinic is how it finds augmenting paths: the path-finding portion of MPM takes only O(n2)O(n^2), giving it a better time complexity than Dinic.

MPM considers vertex capacities rather than edge capacities. In the layered network LL, if the capacity of vertex vv, denoted p(v)p(v), is defined as the smaller of its incoming and outgoing residual capacities, then:

pin(v)=∑(u,v)∈L(c(u,v)−f(u,v))pout(v)=∑(v,u)∈L(c(v,u)−f(v,u))p(v)=min⁡(pin(v),pout(v))\begin{aligned} p_{in}(v) &= \sum\limits_{(u,v) \in L} (c(u, v) - f(u, v)) \\ p_{out}(v) &= \sum\limits_{(v,u) \in L} (c(v, u) - f(v, u)) \\ p(v) &= \min (p_{in}(v), p_{out}(v)) \end{aligned}

We call a vertex rr a reference vertex if and only if p(r)=min⁡p(v)p(r) = \min {p(v)}. For a reference vertex rr, we can always increase the flow passing through rr by p(r)p(r), making its capacity 00. This is because LL is a directed acyclic graph and every vertex in LL has capacity at least p(r)p(r), so there must be a directed path from ss through rr to tt. We simply increase the flow on every edge of this path by p(r)p(r). This path is the augmenting path for this phase. BFS can be used to find augmenting paths. After augmentation, all saturated edges can be removed from LL, because they will not be used again in this phase. Likewise, any vertex other than ss and tt with no outgoing or incoming edges can be removed.

Time Complexity Analysis

Each phase of the MPM algorithm takes O(V2)O(V^2) time, because there are at most VV iterations (at least the selected reference vertex is deleted), and in each iteration we delete all traversed edges except at most VV of them. Summing gives O(V2+E)=O(V2)O(V^2+E)=O(V^2). Since the total number of phases is less than VV, the total running time of MPM is O(V3)O(V^3).

Proof that the Number of Phases Is Less Than V

The MPM algorithm ends in fewer than VV phases. To prove this, we first need to prove two lemmas.

Lemma 1: after each iteration, the distance from ss to every vertex does not decrease, that is, leveli+1[v]≥leveli[v]level_{i+1}[v] \ge level_{i}[v].

Proof: Fix a phase ii and a vertex vv. Consider in GiRG_{i}^R any shortest path from ss to vv, denoted PP. The length of PP is leveli[v]level_{i}[v]. Note that GiRG_{i}^R can contain only backward and forward edges of GiRG_{i}^R. If PP has no backward edge of GiRG_{i}^R, then leveli+1[v]≥leveli[v]level_{i+1}[v] \ge level_{i}[v], because PP is also a path in GiRG_{i}^R. Now suppose that PP has at least one backward edge and its first such edge is (u,w)(u,w). Then leveli+1[u]≥leveli[u]level_{i+1}[u] \ge level_{i}[u] (by the first case). The edge (u,w)(u,w) does not belong to GiRG_{i}^R, so (u,w)(u,w) was affected by the augmenting path of the previous iteration. This means leveli[u]=leveli[w]+1level_{i}[u] = level_{i}[w]+1. In addition, leveli+1[w]=leveli+1[u]+1level_{i+1}[w] = level_{i+1}[u]+1. From these two equations and leveli+1[u]≥leveli[u]level_{i+1}[u] \ge level_{i}[u], we get leveli+1[w]≥leveli[w]+2level_{i+1}[w] \ge level_{i}[w]+2. The remaining part of the path can be handled in the same way.

Lemma 2: leveli+1[t]>leveli[t]level_{i+1}[t] > level_{i}[t].

Proof: Lemma 1 gives leveli+1[t]≥leveli[t]level_{i+1}[t] \ge level_{i}[t]. Suppose leveli+1[t]=leveli[t]level_{i+1}[t] = level_{i}[t]. Note that GiRG_{i}^R can contain only backward and forward edges of GiRG_{i}^R. This means GiRG_{i}^R contains a shortest path that was not blocked by the augmenting path, which is a contradiction.

Implementation

Reference code
struct MPM {
  struct FlowEdge {
    int v, u;
    long long cap, flow;

    FlowEdge() {}

    FlowEdge(int _v, int _u, long long _cap, long long _flow)
        : v(_v), u(_u), cap(_cap), flow(_flow) {}

    FlowEdge(int _v, int _u, long long _cap)
        : v(_v), u(_u), cap(_cap), flow(0ll) {}
  };

  constexpr static long long flow_inf = 1e18;
  vector<FlowEdge> edges;
  vector<char> alive;
  vector<long long> pin, pout;
  vector<list<int>> in, out;
  vector<vector<int>> adj;
  vector<long long> ex;
  int n, m = 0;
  int s, t;
  vector<int> level;
  vector<int> q;
  int qh, qt;

  void resize(int _n) {
    n = _n;
    ex.resize(n);
    q.resize(n);
    pin.resize(n);
    pout.resize(n);
    adj.resize(n);
    level.resize(n);
    in.resize(n);
    out.resize(n);
  }

  MPM() {}

  MPM(int _n, int _s, int _t) {
    resize(_n);
    s = _s;
    t = _t;
  }

  void add_edge(int v, int u, long long cap) {
    edges.push_back(FlowEdge(v, u, cap));
    edges.push_back(FlowEdge(u, v, 0));
    adj[v].push_back(m);
    adj[u].push_back(m + 1);
    m += 2;
  }

  bool bfs() {
    while (qh < qt) {
      int v = q[qh++];
      for (int id : adj[v]) {
        if (edges[id].cap - edges[id].flow < 1) continue;
        if (level[edges[id].u] != -1) continue;
        level[edges[id].u] = level[v] + 1;
        q[qt++] = edges[id].u;
      }
    }
    return level[t] != -1;
  }

  long long pot(int v) { return min(pin[v], pout[v]); }

  void remove_node(int v) {
    for (int i : in[v]) {
      int u = edges[i].v;
      auto it = find(out[u].begin(), out[u].end(), i);
      out[u].erase(it);
      pout[u] -= edges[i].cap - edges[i].flow;
    }
    for (int i : out[v]) {
      int u = edges[i].u;
      auto it = find(in[u].begin(), in[u].end(), i);
      in[u].erase(it);
      pin[u] -= edges[i].cap - edges[i].flow;
    }
  }

  void push(int from, int to, long long f, bool forw) {
    qh = qt = 0;
    ex.assign(n, 0);
    ex[from] = f;
    q[qt++] = from;
    while (qh < qt) {
      int v = q[qh++];
      if (v == to) break;
      long long must = ex[v];
      auto it = forw ? out[v].begin() : in[v].begin();
      while (true) {
        int u = forw ? edges[*it].u : edges[*it].v;
        long long pushed = min(must, edges[*it].cap - edges[*it].flow);
        if (pushed == 0) break;
        if (forw) {
          pout[v] -= pushed;
          pin[u] -= pushed;
        } else {
          pin[v] -= pushed;
          pout[u] -= pushed;
        }
        if (ex[u] == 0) q[qt++] = u;
        ex[u] += pushed;
        edges[*it].flow += pushed;
        edges[(*it) ^ 1].flow -= pushed;
        must -= pushed;
        if (edges[*it].cap - edges[*it].flow == 0) {
          auto jt = it;
          ++jt;
          if (forw) {
            in[u].erase(find(in[u].begin(), in[u].end(), *it));
            out[v].erase(it);
          } else {
            out[u].erase(find(out[u].begin(), out[u].end(), *it));
            in[v].erase(it);
          }
          it = jt;
        } else
          break;
        if (!must) break;
      }
    }
  }

  long long flow() {
    long long ans = 0;
    while (true) {
      pin.assign(n, 0);
      pout.assign(n, 0);
      level.assign(n, -1);
      alive.assign(n, true);
      level[s] = 0;
      qh = 0;
      qt = 1;
      q[0] = s;
      if (!bfs()) break;
      for (int i = 0; i < n; i++) {
        out[i].clear();
        in[i].clear();
      }
      for (int i = 0; i < m; i++) {
        if (edges[i].cap - edges[i].flow == 0) continue;
        int v = edges[i].v, u = edges[i].u;
        if (level[v] + 1 == level[u] && (level[u] < level[t] || u == t)) {
          in[u].push_back(i);
          out[v].push_back(i);
          pin[u] += edges[i].cap - edges[i].flow;
          pout[v] += edges[i].cap - edges[i].flow;
        }
      }
      pin[s] = pout[t] = flow_inf;
      while (true) {
        int v = -1;
        for (int i = 0; i < n; i++) {
          if (!alive[i]) continue;
          if (v == -1 || pot(i) < pot(v)) v = i;
        }
        if (v == -1) break;
        if (pot(v) == 0) {
          alive[v] = false;
          remove_node(v);
          continue;
        }
        long long f = pot(v);
        ans += f;
        push(v, s, f, false);
        push(v, t, f, true);
        alive[v] = false;
        remove_node(v);
      }
    }
    return ans;
  }
};

ISAP

In the Dinic algorithm, we run BFS to build layers after finding augmenting paths each time. Is there a more efficient method?

The answer is the ISAP algorithm introduced below.

Process

As in Dinic, we first run BFS to layer the vertices, but unlike Dinic, we run BFS on the reverse graph, from tt to ss.

After the layering process, we use DFS to find augmenting paths.

The augmentation process is similar to Dinic: we only augment to vertices whose layer is 11 less than the current vertex's layer.

Unlike Dinic, we do not rerun BFS to layer the graph. Instead, we complete the relayering during augmentation.

Specifically, let the layer of vertex ii be did_i. After finishing augmentation at vertex ii, we traverse all outgoing edges of ii in the residual network and find the outgoing vertex jj with the smallest layer. We then set di←dj+1d_i \gets d_j+1. In particular, if ii has no outgoing edge in the residual network, set di←nd_i \gets n.

It is easy to see that when ds≥nd_s \geq n, no augmenting path exists in the graph, so the algorithm can terminate.

As in Dinic, ISAP also has current-arc optimization.

For layer ii, we record the number of vertices as numinum_i. Whenever a vertex's layer changes from xx to yy, we update the numnum array accordingly. If after the update numx=0num_x=0, a gap has appeared in the graph and no augmenting path can be found; the algorithm can terminate immediately (in the implementation, set dsd_s directly to nn). This optimization is called GAP optimization.

Implementation

Reference code
struct Edge {
  int from, to, cap, flow;

  Edge(int u, int v, int c, int f) : from(u), to(v), cap(c), flow(f) {}
};

bool operator<(const Edge& a, const Edge& b) {
  return a.from < b.from || (a.from == b.from && a.to < b.to);
}

struct ISAP {
  int n, m, s, t;
  vector<Edge> edges;
  vector<int> G[MAXN];
  bool vis[MAXN];
  int d[MAXN];
  int cur[MAXN];
  int p[MAXN];
  int num[MAXN];

  void AddEdge(int from, int to, int cap) {
    edges.push_back(Edge(from, to, cap, 0));
    edges.push_back(Edge(to, from, 0, 0));
    m = edges.size();
    G[from].push_back(m - 2);
    G[to].push_back(m - 1);
  }

  bool BFS() {
    memset(vis, 0, sizeof(vis));
    queue<int> Q;
    Q.push(t);
    vis[t] = true;
    d[t] = 0;
    while (!Q.empty()) {
      int x = Q.front();
      Q.pop();
      for (int i = 0; i < G[x].size(); i++) {
        Edge& e = edges[G[x][i] ^ 1];
        if (!vis[e.from] && e.cap > e.flow) {
          vis[e.from] = true;
          d[e.from] = d[x] + 1;
          Q.push(e.from);
        }
      }
    }
    return vis[s];
  }

  void init(int n) {
    this->n = n;
    for (int i = 0; i < n; i++) G[i].clear();
    edges.clear();
  }

  int Augment() {
    int x = t, a = INF;
    while (x != s) {
      Edge& e = edges[p[x]];
      a = min(a, e.cap - e.flow);
      x = edges[p[x]].from;
    }
    x = t;
    while (x != s) {
      edges[p[x]].flow += a;
      edges[p[x] ^ 1].flow -= a;
      x = edges[p[x]].from;
    }
    return a;
  }

  int Maxflow(int s, int t) {
    this->s = s;
    this->t = t;
    int flow = 0;
    BFS();
    memset(num, 0, sizeof(num));
    for (int i = 0; i < n; i++) num[d[i]]++;
    int x = s;
    memset(cur, 0, sizeof(cur));
    while (d[s] < n) {
      if (x == t) {
        flow += Augment();
        x = s;
      }
      int ok = 0;
      for (int i = cur[x]; i < G[x].size(); i++) {
        Edge& e = edges[G[x][i]];
        if (e.cap > e.flow && d[x] == d[e.to] + 1) {
          ok = 1;
          p[e.to] = G[x][i];
          cur[x] = i;
          x = e.to;
          break;
        }
      }
      if (!ok) {
        int m = n - 1;
        for (int i = 0; i < G[x].size(); i++) {
          Edge& e = edges[G[x][i]];
          if (e.cap > e.flow) m = min(m, d[e.to]);
        }
        if (--num[d[x]] == 0) break;
        num[d[x] = m + 1]++;
        cur[x] = 0;
        if (x != s) x = edges[p[x]].from;
      }
    }
    return flow;
  }
};

Push-Relabel Algorithm

This method ignores flow conservation during the computation and updates one vertex at a time to solve maximum flow.

General Preflow-Push Algorithm

We first introduce the main idea of the preflow-push algorithm and a feasible brute-force implementation.

The preflow-push algorithm solves maximum flow by repeatedly updating individual vertices until no vertex needs to be updated.

The flow function maintained during the algorithm does not necessarily satisfy flow conservation. For a vertex u(u∈V−{s,t})u(u\in V-\{s,t\}), we allow the flow entering it to exceed the flow leaving it; the excess is called the excess flow e(u)e(u):

e(u)=∑(x,u)∈Ef(x,u)−∑(u,y)∈Ef(u,y)e(u)=\sum_{(x,u)\in E}f(x,u)-\sum_{(u,y)\in E}f(u,y)

If e(u)>0e(u)>0, vertex uu is called overflowing3. Note that when we mention an overflowing vertex, we exclude ss and tt.

The preflow-push algorithm maintains the height h(u)h(u) of each vertex and stipulates that an overflowing vertex uu may push excess flow only to a vertex whose height is less than uu; if uu has no adjacent vertex whose height is less than uu, modify the height of uu (relabel it).

Height Function4

More precisely, preflow-push maintains the following mapping h:V→Nh:V\to \mathbf{N}:

  • h(s)=∣V∣,h(t)=0h(s)=|V|,h(t)=0
  • ∀(u,v)∈Ef,h(u)≤h(v)+1\forall (u,v)\in E_f,h(u)\leq h(v)+1

We call hh a height function of the residual network Gf=(Vf,Ef)G_f=(V_f,E_f).

Lemma 1: If GfG_f has height function hh, then for any two vertices u,v∈Vu,v\in V, if h(u)>h(v)+1h(u)>h(v)+1, (u,v)(u,v) is not an edge of GfG_f.

The algorithm performs push operations only on edges satisfying h(u)=h(v)+1h(u)=h(v)+1.

Push

Applicable condition: vertex uu is overflowing, and there exists vertex v((u,v)∈Ef,c(u,v)−f(u,v)>0,h(u)=h(v)+1)v((u,v)\in E_f,c(u,v)-f(u,v)>0,h(u)=h(v)+1); the push operation applies to (u,v)(u,v).

We then push as much excess flow as possible from uu to vv. During the push, we care only about the smaller of the excess flow and c(u,v)−f(u,v)c(u,v)-f(u,v); whether vv overflows is irrelevant.

If (u,v)(u,v) becomes saturated after the push, remove it from the residual network.

Relabel

Applicable condition: if vertex uu is overflowing and ∀(u,v)∈Ef,h(u)≤h(v)\forall (u,v)\in E_f,h(u)\leq h(v), then the relabel operation applies to uu.

Then update h(u)h(u) to min⁡(u,v)∈Efh(v)+1\min_{(u,v)\in E_f}h(v)+1.

Initialization

∀(u,v)∈E,  f(u,v)={c(u,v),u=s0,u≠s\forall (u,v)\in E,~~f(u,v)=\begin{cases} c(u,v),&u=s\\ 0,&u\neq s \end{cases} ∀u∈V,  h(u)={∣V∣,u=s0,u≠s\forall u\in V,~~h(u)=\begin{cases} |V|,&u=s\\ 0,&u\neq s \end{cases} e(u)=∑(x,u)∈Ef(x,u)−∑(u,y)∈Ef(u,y)e(u)=\sum_{(x,u)\in E}f(x,u)-\sum_{(u,y)\in E}f(u,y)

The above saturates every (s,v)∈E(s,v)\in E and raises h(s)h(s), making (s,v)∉Ef(s,v)\notin E_f, because h(s)>h(v)h(s)>h(v) and the edge (s,v)(s,v) is already saturated, so there is no need to keep it in the residual network. It also initializes e(s)e(s) to the negative of ∑(s,v)∈Ef(s,v)\sum_{(s,v)\in E}f(s,v).

Process

We scan the entire graph repeatedly; whenever there is a vertex uu satisfying the conditions for a push or relabel operation, we perform the corresponding operation.

As shown, the number is displayed in the middle of each vertex, the height value h(u)h(u) at the lower left, and the excess flow e(u)e(u) at the lower right. The depth of the vertex color also represents its height. Edge weights represent c(u,v)−f(u,v)c(u,v)-f(u,v), and green edges satisfy h(u)=h(v)+1h(u)=h(v)+1; they are edges (u,v)(u,v) of the residual network EfE_f.

p1
p1

Let us briefly review the process of the entire algorithm. Here the author uses a brute-force algorithm that scans for overflowing vertices and updates one whenever it finds one.

p2
p2

The final result

p3
p3

We can see that in the end, some excess flow has returned to ss, and no vertex other than the source and sink is overflowing. The flow function ff now satisfies flow conservation and is a maximum flow; its value is e(t)e(t).

However, the paper5 points out that processing only overflowing vertices with height less than nn still gives the correct maximum-flow value. In that case, though, the preflow at the end of the algorithm does not yet satisfy the properties of a flow function, so the actual flow on each edge cannot be determined.

Implementation

Core code
constexpr int N = 1e4 + 4, M = 1e5 + 5, INF = 0x3f3f3f3f;
int n, m, s, t, maxflow, tot;
int ht[N], ex[N];

void init() {  // Initialization
  for (int i = h[s]; i; i = e[i].nex) {
    const int &v = e[i].t;
    ex[v] = e[i].v, ex[s] -= ex[v], e[i ^ 1].v = e[i].v, e[i].v = 0;
  }
  ht[s] = n;
}

bool push(int ed) {
  const int &u = e[ed ^ 1].t, &v = e[ed].t;
  int flow = min(ex[u], e[ed].v);
  ex[u] -= flow, ex[v] += flow, e[ed].v -= flow, e[ed ^ 1].v += flow;
  return ex[u];  // Return 1 if u is still overflowing
}

void relabel(int u) {
  ht[u] = INF;
  for (int i = h[u]; i; i = e[i].nex)
    if (e[i].v) ht[u] = min(ht[u], ht[e[i].t]);
  ++ht[u];
}

HLPP Algorithm

The Highest Label Preflow Push algorithm prioritizes the overflowing vertex with the greatest height whenever it selects a vertex in the general preflow-push algorithm above. Its time complexity is O(n2m)O(n^2\sqrt m).

Process

Specifically, the HLPP algorithm proceeds as follows:

  1. Initialize based on the preflow-push algorithm.
  2. Select the highest-height overflowing vertex uu, and push along every edge through which it can push.
  3. If uu is still overflowing, relabel it and return to step 2.
  4. If there is no overflowing vertex, terminate the algorithm.

A paper testing the practical performance of maximum-flow algorithms6 shows that preflow-based algorithms actually spend a considerable portion of their time on relabeling. We introduce below two optimizations from the paper7 that can significantly reduce the number of relabel operations.

BFS Optimization

The upper bound of HLPP is O(n2m)O(n^2\sqrt m), but it is relatively tight in practice. We can optimize the initial heights. Specifically, initialize h(u)h(u) to the shortest distance from uu to tt; in particular, h(s)=nh(s)=n.

During BFS, we also check graph connectivity and rule out cases with no solution.

GAP Optimization

The push condition in HLPP is h(u)=h(v)+1h(u)=h(v)+1. If at some point in the algorithm there is a kk such that the number of vertices with h(u)=kh(u)=k is 00, then vertices with h(u)>kh(u)>k can never push excess flow to tt and can only send it back to ss. We therefore immediately raise their heights to at least n+1n+1, so that they can push back to ss as quickly as possible and reduce relabel operations.

The implementation below follows the method in paper6 and uses N∗2−1N*2-1 buckets B, where B[i] records all overflowing vertices currently at height ii. It includes the two optimizations above and processes only overflowing vertices with height less than nn.

It is worth noting that the buckets in paper6 use stacks based on linked lists, whereas the default container for stack in the STL is deque. Simple tests show little difference in the actual running efficiency of vector, deque, and list for this problem.

Implementation

LuoguP4722 [Template] Maximum Flow, Enhanced Version/Preflow-Push
#include <cstdio>
#include <cstring>
#include <queue>
#include <stack>
using namespace std;
constexpr int N = 1200, M = 120000, INF = 0x3f3f3f3f;
int n, m, s, t;

struct qxx {
  int nex, t;
  long long v;
};

qxx e[M * 2 + 1];
int h[N + 1], cnt = 1;

void add_path(int f, int t, long long v) {
  e[++cnt] = qxx{h[f], t, v}, h[f] = cnt;
}

void add_flow(int f, int t, long long v) {
  add_path(f, t, v);
  add_path(t, f, 0);
}

int ht[N + 1];        // height;
long long ex[N + 1];  // excess flow;
int gap[N];           // GAP optimization. gap[i] is the number of vertices at height i
stack<int> B[N];      // Bucket B[i] stores all v with ht[v]==i
int level = 0;        // maximum height of an overflowing vertex

int push(int u) {      // Push as much excess flow as possible through usable edges
  bool init = u == s;  // whether this is initialization
  for (int i = h[u]; i; i = e[i].nex) {
    const int &v = e[i].t;
    const long long &w = e[i].v;
    // During initialization, do not require a height difference of 1
    if (!w || (init == false && ht[u] != ht[v] + 1) || ht[v] == INF) continue;
    long long k = init ? w : min(w, ex[u]);
    // Take the minimum of residual capacity and excess flow; initialization may make the source's excess negative.
    if (v != s && v != t && !ex[v]) B[ht[v]].push(v), level = max(level, ht[v]);
    ex[u] -= k, ex[v] += k, e[i].v -= k, e[i ^ 1].v += k;  // push
    if (!ex[u]) return 0;  // Return if all flow has been pushed
  }
  return 1;
}

void relabel(int u) {  // Relabel (height)
  ht[u] = INF;
  for (int i = h[u]; i; i = e[i].nex)
    if (e[i].v) ht[u] = min(ht[u], ht[e[i].t]);
  if (++ht[u] < n) {  // Process only vertices with height less than n
    B[ht[u]].push(u);
    level = max(level, ht[u]);
    ++gap[ht[u]];  // New height; update GAP
  }
}

bool bfs_init() {
  memset(ht, 0x3f, sizeof(ht));
  queue<int> q;
  q.push(t), ht[t] = 0;
  while (q.size()) {  // Reverse BFS; enqueue an unvisited vertex
    int u = q.front();
    q.pop();
    for (int i = h[u]; i; i = e[i].nex) {
      const int &v = e[i].t;
      if (e[i ^ 1].v && ht[v] > ht[u] + 1) ht[v] = ht[u] + 1, q.push(v);
    }
  }
  return ht[s] != INF;  // Return 0 if the graph is disconnected
}

// Select one of the vertices at the current maximum height; return 0 if no overflowing vertex remains
int select() {
  while (level > -1 && B[level].size() == 0) level--;
  return level == -1 ? 0 : B[level].top();
}

long long hlpp() {            // Return the maximum flow
  if (!bfs_init()) return 0;  // The graph is disconnected
  memset(gap, 0, sizeof(gap));
  for (int i = 1; i <= n; i++)
    if (ht[i] != INF) gap[ht[i]]++;  // Initialize GAP
  ht[s] = n;
  push(s);  // Initialize the preflow
  int u;
  while ((u = select())) {
    B[level].pop();
    if (push(u)) {  // Still overflowing
      if (!--gap[ht[u]])
        for (int i = 1; i <= n; i++)
          if (i != s && ht[i] > ht[u] && ht[i] < n + 1)
            ht[i] = n + 1;  // Vertices relabeled to n+1 here are not overflowing
      relabel(u);
    }
  }
  return ex[t];
}

int main() {
  scanf("%d%d%d%d", &n, &m, &s, &t);
  for (int i = 1, u, v, w; i <= m; i++) {
    scanf("%d%d%d", &u, &v, &w);
    add_flow(u, v, w);
  }
  printf("%lld", hlpp());
  return 0;
}

Feel the execution process

HLPP
HLPP

Relabel(4) is performed from pic13 to pic14, together with GAP optimization.

Footnotes

Footnotes

  1. http://pisces.ck.tp.edu.tw/~peng/index.php?action=showfile&file=f6cdf7ef750d7dc79c7d599b942acbaaee86a2e3e ↩

  2. https://people.orie.cornell.edu/dpw/orie633/LectureNotes/lecture9.pdf ↩

  3. English-language literature usually calls it “active”. ↩

  4. In English-language literature, the height of a vertex is usually called a “distance label”. The term “height” used here comes from the relevant chapter of Introduction to Algorithms. You can find the reason for this choice in footnote P432 of the Chinese edition of Introduction to Algorithms (3rd edition) published by China Machine Press. ↩

  5. Cherkassky B V, Goldberg A V. On implementing push-relabel method for the maximum flow problem[C]//International Conference on Integer Programming and Combinatorial Optimization. Springer, Berlin, Heidelberg, 1995: 157-171. ↩

  6. Ahuja R K, Kodialam M, Mishra A K, et al. Computational investigations of maximum flow algorithms[J]. European Journal of Operational Research, 1997, 97(3): 509-542. ↩ ↩2 ↩3

  7. Derigs U, Meier W. Implementing Goldberg's max-flow-algorithm—A computational investigation[J]. Zeitschrift für Operations Research, 1989, 33(6): 383-403. ↩

This content comes from OI Wiki: Max Flow. See the original for authors and revision history. AIOJ has adapted the formatting, in-site links, and resource paths.

Content is shared under CC BY-SA 4.0 and SATA license; additional terms are governed by the original notice. Thanks to the OI Wiki authors and contributors; per the SATA terms, please visit the OI Wiki repository and star it to show your support.