An – cut is a set , ; its capacity is the total capacity of edges leaving . By max-flow min-cut (Max Flow) the minimum equals the maximum flow, and after any max flow the residual graph describes all minimum cuts at once.

All minimum cuts from one max flow

Fix a maximum flow and its residual graph .

  • is the source side of a minimum cut iff , and no residual edge leaves (Picard–Queyranne). So minimum cuts are the closed sets of separating from , which form a lattice under and .
  • Smallest source side: = vertices reachable from in . Largest: , where = vertices that can reach in .
  • The minimum cut is unique iff .
  • Edge is in every minimum cut iff and .
  • Edge is in some minimum cut iff it is saturated and , lie in different SCCs of .
  • Counting minimum cuts is p-hard in general (closed sets of a DAG), but enumeration or checks per edge are linear after the flow.
struct CutInfo {                                       // call after D.maxflow(s, t)
    vector<char> fromS, toT; vector<int> comp;
    CutInfo(const Dinic& D, int s, int t) {
        int n = sz(D.g); fromS.assign(n, 0); toT.assign(n, 0);
        auto bfs = [&](int src, vector<char>& vis, bool rev) {
            vector<int> st{src}; vis[src] = 1;
            while (sz(st)) {
                int u = st.back(); st.pop_back();
                for (int id : D.g[u]) {
                    int v = D.e[id].to; ll c = rev ? D.e[id ^ 1].cap : D.e[id].cap;   // rev: residual edge v -> u
                    if (c > 0 && !vis[v]) vis[v] = 1, st.push_back(v);
                }
            }
        };
        bfs(s, fromS, false); bfs(t, toT, true);
        comp.assign(n, -1); vector<int> low(n), num(n, -1), stk; int timer = 0, nc = 0;   // Tarjan SCC on G_f
        auto dfs = [&](auto& self, int u) -> void {
            num[u] = low[u] = timer++; stk.push_back(u);
            for (int id : D.g[u]) if (D.e[id].cap > 0) {
                int v = D.e[id].to;
                if (num[v] < 0) self(self, v), low[u] = min(low[u], low[v]);
                else if (comp[v] < 0) low[u] = min(low[u], num[v]);
            }
            if (low[u] == num[u]) { int v; do { v = stk.back(); stk.pop_back(); comp[v] = nc; } while (v != u); nc++; }
        };
        for (int v = 0; v < n; v++) if (num[v] < 0) dfs(dfs, v);
    }
    bool unique() const { for (int v = 0; v < sz(fromS); v++) if (!fromS[v] && !toT[v]) return false; return true; }
    bool inEvery(int u, int v) const { return fromS[u] && toT[v]; }
    bool inSome(const Dinic& D, int id) const {        // id = forward edge id u -> v with positive capacity
        int u = D.e[id ^ 1].to, v = D.e[id].to;
        return D.e[id].cap == 0 && comp[u] != comp[v];
    }
};

Menger and connectivity

  • Edge version: the maximum number of edge-disjoint – paths equals the minimum number of edges whose removal separates from (unit capacities).
  • Vertex version: for non-adjacent , the maximum number of internally vertex-disjoint paths equals the minimum vertex separator (split into with capacity 1, other edges ).
  • Vertex connectivity : a min vertex separator misses one of any vertices, so trying sources against all non-adjacent targets suffices. Edge connectivity is a global min cut (Global Min Cut). Whitney: .
int vertexConnectivity(int n, const vector<vector<int>>& adj) {   // undirected, adjacency matrix 0/1
    int best = n - 1;                                              // complete graph
    for (int s = 0; s <= min(best, n - 1); s++)
        for (int t = 0; t < n; t++) {
            if (t == s || adj[s][t]) continue;
            Dinic D(2 * n); const ll INF = n;
            for (int v = 0; v < n; v++) D.addEdge(2 * v, 2 * v + 1, v == s || v == t ? INF : 1);
            for (int u = 0; u < n; u++) for (int v = 0; v < n; v++) if (adj[u][v]) D.addEdge(2 * u + 1, 2 * v, INF);
            best = min<ll>(best, D.maxflow(2 * s + 1, 2 * t));
        }
    return best;
}

Tricks

  • Minimum cut with fewest edges: use capacities ; the cut value divided by is the old minimum and the remainder is the number of edges.
  • Lexicographic / “edge in a min cut?”: after computing , raising by one changes the max flow iff is in every min cut; deleting drops it by iff is in some min cut (the residual checks above do both in linear time).
  • Planar – min cut = shortest path in the dual (grid “block every path” problems): put and on the outer face, split the outer face into two dual vertices, and run Dijkstra on the dual with edge weights = capacities. instead of a flow.
  • Cuts with a constraint ” and on the same side”: edges with ; ” on the source side” is with .
  • Minimum -cut, multiway cut: NP-hard in general; for fixed tiny use Gomory–Hu heuristics (a approximation by taking the lightest tree edges).
  • Gomory–Hu on a planar graph builds the same all-pairs min-cut tree in total instead of general max-flow calls, by exploiting planar duality (each min cut corresponds to a shortest cycle in the dual graph, found via multiple-source shortest paths). Cited here as the planar specialisation of the Gomory–Hu tree above; a from-scratch implementation is rarely worth it outside the handful of contests that specifically require it.