Push as much flow as possible from to through capacitated directed edges. Every algorithm works on the residual graph (remaining capacity forward, already-sent flow backward) and stops when is unreachable; then the reachable set is a minimum cut, and max flow = min cut. Write Dinic: in general, for unit-capacity bipartite matching, for unit capacities, and much faster in practice. Modelling is in Flow Modeling.
Dinic
struct Dinic {
struct E { int to; ll cap; };
vector<E> e; vector<ll> orig; vector<vector<int>> g; vector<int> lvl, it;
Dinic(int n) : g(n), lvl(n), it(n) {}
int addEdge(int u, int v, ll c, ll rc = 0) { // returns the id of the forward edge
g[u].push_back(sz(e)); e.push_back({v, c}); orig.push_back(c);
g[v].push_back(sz(e)); e.push_back({u, rc}); orig.push_back(rc);
return sz(e) - 2;
}
bool bfs(int s, int t) {
fill(all(lvl), -1); lvl[s] = 0; queue<int> q; q.push(s);
while (sz(q)) { int u = q.front(); q.pop(); for (int id : g[u]) if (e[id].cap > 0 && lvl[e[id].to] < 0) lvl[e[id].to] = lvl[u] + 1, q.push(e[id].to); }
return lvl[t] >= 0;
}
ll dfs(int u, int t, ll f) {
if (u == t) return f;
ll pushed = 0;
for (int& i = it[u]; i < sz(g[u]); i++) {
int id = g[u][i], v = e[id].to;
if (e[id].cap <= 0 || lvl[v] != lvl[u] + 1) continue;
ll d = dfs(v, t, min(f - pushed, e[id].cap));
if (d) { e[id].cap -= d; e[id ^ 1].cap += d; pushed += d; if (pushed == f) return f; }
}
return pushed;
}
ll maxflow(int s, int t) {
ll flow = 0;
while (bfs(s, t)) { fill(all(it), 0); flow += dfs(s, t, LLONG_MAX); }
return flow;
}
ll flowOn(int id) const { return orig[id] - e[id].cap; } // flow on a forward edge
bool sourceSide(int v) const { return lvl[v] >= 0; } // after maxflow: S side of a minimum cut
};- Undirected edge of capacity :
addEdge(u, v, c, c). - Recursion depth is the level count, at most .
- Capacity scaling (run phases only on edges with residual , halving ) gives and helps on adversarial inputs with huge capacities.
- Push–relabel (highest label + gap heuristic) is the alternative for dense or hard graphs (Push-Relabel).
- Two implementation details do all the work above the naive DFS: storing each edge with its reverse at index
id ^ 1(so pushing flow back needs no lookup), and the current-arc pointerit[u](so a phase never re-scans an edge it already gave up on). Drop either and Dinic degrades toward plain Ford–Fulkerson. - ISAP (Improved Shortest Augmenting Path) is a related variant that maintains the level graph incrementally instead of rebuilding it every phase (via a gap heuristic on vertex levels), saving a constant factor in practice. Link-cut trees can push the blocking-flow step itself to by representing the level graph as a dynamic forest, but this is essentially never worth the implementation cost in a contest.
Proof: max-flow min-cut and termination
Weak duality: for any flow and cut with , , the flow value equals (flow out of ) − (flow into ) . Strong duality: when no augmenting path exists, let be the vertices reachable from in the residual graph. Every edge from to is saturated and every edge from to carries zero flow (otherwise its reverse residual edge would extend ), so the flow value equals ; by weak duality both are optimal. Integrality: with integer capacities every augmentation is integral, so an integral maximum flow exists. Dinic’s phases: let be the BFS distance from to in the residual graph. A blocking flow on the level graph saturates every shortest augmenting path; new residual edges point from level to level , so any augmenting path afterwards uses a backward or skipping edge and has length . Hence at most phases. In one phase, the current-arc pointer means each edge is abandoned at most once, and each successful path has edges, giving per phase and total.
Proof sketch: on unit networks
In a unit network (unit capacities, every vertex other than has in-degree 1 or out-degree 1, e.g. bipartite matching), a phase costs . After phases, the shortest augmenting path has length ; the remaining flow decomposes into vertex-disjoint augmenting paths in the residual graph, each of length , so there are at most of them and at most more phases. Total . For general unit-capacity graphs the same argument with edge-disjoint paths gives , and with a level-cut counting argument .
Older algorithms (for understanding)
| Algorithm | Augment along | Time |
|---|---|---|
| Ford–Fulkerson | any path | , may not terminate with irrational capacities |
| Edmonds–Karp | shortest path (BFS) | |
| capacity scaling | paths with residual | |
| Dinic | blocking flows on level graphs | |
| MPM (Malhotra–Pramodh-Kumar–Maheshwari) | blocking flow by saturating the vertex of least throughput | |
| push–relabel (FIFO / highest label) | local pushes | / |
| Orlin / KRT | — | , theoretical |
Proof: Edmonds–Karp performs augmentations
BFS distances from in the residual graph never decrease (the same argument as Dinic’s: augmenting along a shortest path only adds reverse edges going one level down). An edge is critical (saturated by an augmentation) only when ; before it can be critical again, flow must be sent back along , which requires at that time, so grew by at least 2. Distances are at most , so each edge is critical times, and each augmentation has a critical edge: augmentations of each.
Reading the answer
- Flow on each edge:
flowOn(id); decompose into at most paths and cycles by repeatedly walking positive-flow edges from . - Minimum cut: vertices with
sourceSide(v)after the final BFS; cut edges are saturated edges from to . - Is edge in some / every min cut: Min Cut.
- Incremental flow: after adding an edge or raising a capacity, keep the residual graph and just run more phases; decreasing a capacity may require pushing flow back first.