Flow Across a Cut, Weak Duality & The Three-Way Equivalence
Let $(S, T)$ be any partition of $V$ with $s \in S$ and $t \in T$. The capacity of the cut $c(S, T)$ counts only directed edges going forward from $S$ to $T$:
$$c(S, T) = \sum_{u \in S} \sum_{v \in T} c(u, v)$$By summing the flow conservation equation over all $u \in S$, internal edges $(x, y) \in S \times S$ cancel out in pairs, yielding the Net Flow Across a Cut Lemma:
$$|f| = f(S, T) - f(T, S) = \sum_{u \in S, v \in T} f(u, v) - \sum_{v \in T, u \in S} f(v, u) \le \sum_{u \in S, v \in T} c(u, v) = c(S, T)$$For any flow $f$ in network $G = (V, E, c, s, t)$, the following three statements are logically equivalent:
- (i) Optimality: $f$ is a maximum flow in $G$.
- (ii) Termination: The residual network $G_f$ contains no augmenting path from $s$ to $t$.
- (iii) Strong Duality Certificate: $|f| = c(S, T)$ for some $s$-$t$ cut $(S, T)$ of $G$ (which is therefore a minimum cut).
Proof of (ii) $\implies$ (iii): Suppose $G_f$ has no $s \leadsto t$ path. Define $S = \{v \in V : s \text{ can reach } v \text{ in } G_f\}$ and $T = V \setminus S$. Clearly $s \in S$ and $t \in T$. For any pair $(u, v)$ with $u \in S, v \in T$:
• If $(u, v) \in E$, then $c_f(u, v) = c(u, v) - f(u, v) = 0$ (otherwise $v$ would be reachable from $u \in S$), so $f(u, v) = c(u, v)$ (every forward cut edge is saturated).
• If $(v, u) \in E$, then $c_f(u, v) = f(v, u) = 0$ (otherwise $v$ would be reachable via the backward residual edge), so no flow returns from $T$ to $S$.
Substituting both into the Net Flow Lemma gives $|f| = f(S, T) - f(T, S) = c(S, T) - 0 = c(S, T)$. $\blacksquare$
Visualize Dinic’s BFS level graph L_G and current-arc DFS blocking flows alongside backward residual arcs (v→u), extract the saturated S-T minimum cut, or step through Hopcroft-Karp disjoint shortest augmenting paths.
Invariant & State
The Integrality Theorem
Because Ford-Fulkerson initializes $f = 0$ and each iteration computes $\Delta = \min_{e \in P} c_f(e)$ via subtraction and minimum operations, if all capacities $c(e) \in \mathbb{Z}_{\ge 0}$ are integers, then $\Delta$ is a positive integer at every step. This yields the Integrality Theorem: every network with integer capacities admits an integer maximum flow $f^*(e) \in \mathbb{Z}$. This single fact allows us to solve discrete combinatorial matching and assignment problems using continuous network flow!
A common interview mistake is extracting the minimum cut by collecting all edges with $f(u, v) == c(u, v)$. Many internal edges inside $S \times S$ or $T \times T$ may also be $100\%$ saturated without lying on the minimum cut! The canonical extraction runs a final BFS from $s$ over edges with $c_f > 0$ to mark inS[u], and then outputs all original directed edges $(u, v) \in E$ where inS[u] && !inS[v].
| Operation | Algorithmic Procedure | Time Complexity | Space Complexity |
|---|---|---|---|
| Verify Flow Optimality | BFS from $s$ in $G_f$ ($c_f > 0$); check if $t$ is unvisited | $O(V + E)$ | $O(V)$ |
| Extract Vertex Partition $(S, T)$ | Collect S = {u : vis[u]} after residual BFS from $s$ | $O(V + E)$ | $O(V)$ |
| Extract Min-Cut Edge Set | Scan $e = (u, v) \in E$ with vis[u] && !vis[v] | $O(E)$ | $O(|\text{Cut}|)$ |
Production C++20 Implementation: Canonical Min-Cut Partition & Edge Extractor
#include <vector>
#include <queue>
#include <utility>
struct ResidualNetwork {
struct Edge {
int from, to;
long long cap, flow;
};
int n;
std::vector<Edge> edges;
std::vector<std::vector<int>> adj;
struct MinCutResult {
std::vector<uint8_t> inSourceSide; // 1 if u in S, 0 if u in T
std::vector<std::pair<int, int>> cutEdges;
long long cutCapacity = 0;
};
// Call AFTER maxFlow(s, t) has terminated
MinCutResult extractMinCut(int s) const {
MinCutResult res;
res.inSourceSide.assign(n, 0);
std::queue<int> q;
q.push(s);
res.inSourceSide[s] = 1;
while (!q.empty()) {
int u = q.front();
q.pop();
for (int id : adj[u]) {
const auto& e = edges[id];
long long residual = e.cap - e.flow;
if (residual > 0 && !res.inSourceSide[e.to]) {
res.inSourceSide[e.to] = 1;
q.push(e.to);
}
}
}
// Only inspect original forward edges (even indices 0, 2, 4, ...)
for (size_t id = 0; id < edges.size(); id += 2) {
const auto& e = edges[id];
if (res.inSourceSide[e.from] && !res.inSourceSide[e.to]) {
res.cutEdges.emplace_back(e.from, e.to);
res.cutCapacity += e.cap;
}
}
return res;
}
};
Remember that cut capacity $c(S, T) = \sum_{u \in S, v \in T} c(u, v)$ is directional! An original edge $(v, u)$ directed from $T$ into $S$ has capacity $c(v, u)$ that does not contribute to $c(S, T)$ (and at optimality carries $f(v, u) = 0$ flow). Only original edges directed from $S$ to $T$ (even indices in our paired array) belong to the $s$-$t$ cut.