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 pointer it[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.

Older algorithms (for understanding)

AlgorithmAugment alongTime
Ford–Fulkersonany path, may not terminate with irrational capacities
Edmonds–Karpshortest path (BFS)
capacity scalingpaths with residual
Dinicblocking 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

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.