Given a directed graph with edge capacities, a source and a sink , push as much flow as possible from to subject to capacity limits and conservation at every other vertex.

Max-Flow Min-Cut Theorem

The maximum flow value equals the minimum capacity of an - cut.

This is why so many problems reduce to flow: anything phrased as “minimum cost to separate” or “maximum number of disjoint things” is one of the two sides of this equality.

Which algorithm

AlgorithmTimeUse
Ford-Fulkersonnever — pseudo-polynomial
Edmonds-Karpteaching
Dinic; unit capsthe default
Push-relabel (highest label + gap)dense graphs, huge capacities
MPMdense graphs

Write Dinic. Its worst case is pessimistic; on bipartite matching it is provably , and on typical contest graphs it is far faster than its bound.

Dinic

struct Dinic {
    struct E { int to; long long cap; };
    vector<E> es;
    vector<vector<int>> g;
    vector<int> level, it;
    int n;
 
    Dinic(int n) : g(n), level(n), it(n), n(n) {}
    void addEdge(int u, int v, long long c) {
        g[u].push_back(es.size()); es.push_back({v, c});
        g[v].push_back(es.size()); es.push_back({u, 0});   // reverse, cap 0
    }
    bool bfs(int s, int t) {
        fill(level.begin(), level.end(), -1);
        queue<int> q; level[s] = 0; q.push(s);
        while (!q.empty()) {
            int u = q.front(); q.pop();
            for (int id : g[u])
                if (es[id].cap > 0 && level[es[id].to] < 0) {
                    level[es[id].to] = level[u] + 1;
                    q.push(es[id].to);
                }
        }
        return level[t] >= 0;
    }
    long long dfs(int u, int t, long long f) {
        if (u == t) return f;
        for (int& i = it[u]; i < (int)g[u].size(); i++) {
            int id = g[u][i], v = es[id].to;
            if (es[id].cap <= 0 || level[v] != level[u] + 1) continue;
            long long d = dfs(v, t, min(f, es[id].cap));
            if (d > 0) { es[id].cap -= d; es[id ^ 1].cap += d; return d; }
        }
        return 0;
    }
    long long maxflow(int s, int t) {
        long long flow = 0;
        while (bfs(s, t)) {
            fill(it.begin(), it.end(), 0);
            while (long long f = dfs(s, t, LLONG_MAX)) flow += f;
        }
        return flow;
    }
};

Two details do all the work: storing each edge with its reverse at index id ^ 1, and the current-arc pointer it[u] which prevents re-scanning saturated edges within a phase.

Modelling patterns

ProblemConstruction
Bipartite matching cap 1, cap 1, cap 1
Vertex capacitiessplit into with that capacity
Multiple sources/sinkssuper-source and super-sink with infinite edges
Vertex-disjoint pathssplit vertices, capacities 1, answer = max flow
Edge-disjoint pathscapacities 1, answer = max flow (Menger)
Minimum path cover of a DAG − maximum bipartite matching of the split graph
Maximum closure / project selectionprofits from , costs to , prerequisites as edges; answer = total profit − min cut
Minimum vertex cover (bipartite)= max matching (König); recover it from the min cut
Maximum independent set (bipartite) − max matching
Assignment with capacitiescapacities on the and edges
Lower bounds on edgestransform to a circulation problem

Project selection is the highest-value pattern: whenever the problem is “choose a set of items, some give profit, some cost, with ‘if you take you must take ’ constraints”, it is a min cut.

Recovering the min cut

After maxflow, BFS from in the residual graph. The reachable set and its complement form a minimum cut; the cut edges are those from to with zero residual capacity.

Integrality

With integer capacities, max flow has an integral optimal solution — which is why matching and disjoint-path problems get 0/1 answers for free. This fails for min-cost flow only when capacities are non-integral.

See also: Minimum Cut · Min-Cost Flow · Dinic