Edges have capacities and per-unit costs; send a required amount (or the maximum) from to as cheaply as possible, or more generally route given supplies and demands with lower bounds. A flow is optimal iff its residual graph has no negative cycle, iff there are potentials with reduced costs on every residual edge.

MethodTimeUse
successive shortest paths (SSP) + potentialsthe default; is usually small (matchings)
SSP with Dijkstradense graphs
capacity scaling (-flow)huge capacities, supplies, negative cycles
cost scaling (push–relabel)large graphs, any costs
network simplexno good bound, fastest in practicelarge graphs, any costs
min-mean cycle cancellingstrongly polynomialtheory

Successive shortest paths

Augment along a cheapest residual path each time. Dijkstra works on reduced costs once potentials are valid: start from Bellman–Ford if some cost is negative (no negative cycle allowed), else from zero, and add the distances to the potentials after each round.

struct MCMF {
    struct E { int to; ll cap, cost; };
    int n; vector<E> e; vector<vector<int>> g; vector<ll> pot, dist; vector<int> pe;
    MCMF(int n) : n(n), g(n), pot(n), dist(n), pe(n) {}
    int addEdge(int u, int v, ll cap, ll cost) {
        g[u].push_back(sz(e)); e.push_back({v, cap, cost});
        g[v].push_back(sz(e)); e.push_back({u, 0, -cost});
        return sz(e) - 2;
    }
    void initPotentials(int s) {                               // only needed with negative costs
        const ll INF = LLONG_MAX; fill(all(pot), INF); pot[s] = 0;
        for (int it = 0; it < n; it++) {
            bool ch = false;
            for (int u = 0; u < n; u++) if (pot[u] != INF)
                for (int id : g[u]) if (e[id].cap > 0 && pot[u] + e[id].cost < pot[e[id].to]) pot[e[id].to] = pot[u] + e[id].cost, ch = true;
            if (!ch) break;
        }
        for (ll& p : pot) if (p == INF) p = 0;                 // unreachable vertices stay unreachable
    }
    pair<ll, ll> run(int s, int t, ll need = LLONG_MAX) {      // {flow, cost}
        ll flow = 0, cost = 0; const ll INF = LLONG_MAX;
        while (flow < need) {
            fill(all(dist), INF); dist[s] = 0;
            priority_queue<pair<ll, int>, vector<pair<ll, int>>, greater<>> pq; pq.push({0, s});
            while (sz(pq)) {
                auto [d, u] = pq.top(); pq.pop();
                if (d > dist[u]) continue;
                for (int id : g[u]) {
                    if (e[id].cap <= 0) continue;
                    ll nd = d + e[id].cost + pot[u] - pot[e[id].to];
                    if (nd < dist[e[id].to]) dist[e[id].to] = nd, pe[e[id].to] = id, pq.push({nd, e[id].to});
                }
            }
            if (dist[t] == INF) break;
            for (int v = 0; v < n; v++) if (dist[v] < INF) pot[v] += dist[v];
            ll push = need - flow;
            for (int v = t; v != s; v = e[pe[v] ^ 1].to) push = min(push, e[pe[v]].cap);
            for (int v = t; v != s; v = e[pe[v] ^ 1].to) e[pe[v]].cap -= push, e[pe[v] ^ 1].cap += push, cost += push * e[pe[v]].cost;
            flow += push;
        }
        return {flow, cost};
    }
};
  • Cheapest flow of any value: stop as soon as the shortest path has non-negative cost ( after the update).
  • The cost of the first units is convex in (path costs are non-decreasing), so SSP answers “minimum cost for every ” in one run, and it plugs into Aliens / slope tricks.
  • Max-cost flow: negate costs (then initialise potentials).

Modelling with costs

RequirementConstruction
assignment / weighted bipartite matchingAssignment Problem (Hungarian is faster)
transportation (supplies, demands, shipping costs) supplier (supply, 0), supplier consumer (cap, cost), consumer (demand, 0)
convex cost on an edgeunit-capacity parallel edges with costs (increasing, so they fill in order)
disjoint paths of minimum total lengthunit capacities (split vertices for vertex-disjoint), flow (Suurballe)
interval scheduling with weights, machineschain with capacity and cost 0, each interval with capacity 1 and cost
reward for a choicenegative cost; initialise potentials (or shift costs by a constant per unit if every path uses one such edge)
lower bounds, supplies, negative cyclesmin-cost -flow below

Min-cost -flow (capacity scaling, negative cycles allowed)

Supplies (positive = produce), edges with and any integer costs. Each vertex is joined to a virtual root by an edge with cost (big-M) to force supplies through; capacities are revealed bit by bit, and each new is pushed along shortest paths with potentials.

struct MinCostBFlow {
    struct E { int src, dst; ll flow, cap, cost; };
    int N; ll INF = 1; i128 base = 0;
    vector<ll> B, P, lower; vector<vector<int>> G; vector<E> e; vector<int> pre;
    MinCostBFlow(int n) : N(n), B(n), P(n + 1), G(n + 1), pre(n + 1) {}
    int ae(int a, int b, ll l, ll u, ll c) {
        G[a].push_back(sz(e)); e.push_back({a, b, 0, u - l, c});
        G[b].push_back(sz(e)); e.push_back({b, a, 0, 0, -c});
        base += (i128)l * c;
        if (a < N) B[a] -= l;
        if (b < N) B[b] += l;
        return sz(e) - 2;
    }
    int addEdge(int u, int v, ll lo, ll hi, ll cost) { lower.push_back(lo); return ae(u, v, lo, hi, cost) / 2; }
    void addSupply(int v, ll b) { B[v] += b; }
    void pin(int x) {                                         // shortest paths from x, folded into P
        priority_queue<pair<ll, int>, vector<pair<ll, int>>, greater<>> pq; vector<ll> add(N + 1);
        for (int i = 0; i <= N; i++) add[i] = -P[i];
        add[x] -= 3 * INF; pre[x] = -1;
        for (int i = 0; i <= N; i++) pq.push({add[i], i});
        while (sz(pq)) {
            auto [d, u] = pq.top(); pq.pop();
            if (d > add[u]) continue;
            for (int id : G[u]) if (e[id].flow < e[id].cap) {
                ll nd = d + e[id].cost + P[e[id].src] - P[e[id].dst];
                if (nd < add[e[id].dst]) add[e[id].dst] = nd, pre[e[id].dst] = id, pq.push({nd, e[id].dst});
            }
        }
        for (int i = 0; i <= N; i++) P[i] += add[i];
    }
    void addCap(int id, ll d) {
        if (e[id].flow == e[id].cap) {
            pin(e[id].dst);
            if (P[e[id].src] - P[e[id].dst] + e[id].cost < 0) {   // closing a negative cycle: push d around it
                for (int x = e[id].src; pre[x] != -1; x = e[pre[x]].src) e[pre[x]].flow += d, e[pre[x] ^ 1].flow -= d;
                e[id].flow += d, e[id ^ 1].flow -= d;
            }
        }
        e[id].cap += d;
    }
    optional<i128> solve() {                                  // nullopt if infeasible
        int M = sz(e);
        for (int i = 0; i < M; i += 2) INF += abs(e[i].cost);
        for (int i = 0; i < N; i++) { int a = N, b = i; ll c = B[i]; if (c < 0) c = -c, swap(a, b); ae(a, b, 0, c, -INF); }
        vector<ll> caps(sz(e)); ll mx = 1;
        for (int i = 0; i < sz(e); i++) swap(caps[i], e[i].cap), mx = max(mx, caps[i]);
        ll d = 1; while (d < mx) d *= 2;
        for (; d; d /= 2) for (int i = 0; i < sz(e); i++) if (caps[i] & d) addCap(i, d);
        for (int i = 0; i < N; i++) if (e[M + 2 * i].flow < e[M + 2 * i].cap) return nullopt;
        i128 ans = base;
        for (int i = 0; i < M; i += 2) ans += (i128)e[i].flow * e[i].cost;
        return ans;
    }
    ll flowOn(int id) const { return lower[id] + e[2 * id].flow; }
    ll potential(int v) const { return P[v]; }
};

– min-cost flow with negative cycles allowed elsewhere in the graph: add an edge with cost (larger in magnitude than any simple path cost) and capacity up to the max flow value, turning the problem into a pure circulation (zero supplies/demands) that this structure solves directly. (Port of Benq’s capacity scaling, verified on Library Checker “Minimum Cost b-flow”.)

Network simplex

Keep a spanning tree basis (rooted at a virtual vertex joined to all by big-M edges); repeatedly bring in an edge with negative reduced cost (block pivoting over edges), push around the cycle it closes in the tree, and drop the bottleneck edge. The fastest practical min-cost flow; same interface as above.

struct NetworkSimplex {
    struct E { int src, dst; ll flow, cap, cost; };
    ll INF = 1; i128 delta = 0; int n;
    vector<ll> B, P, lower; vector<E> e; vector<int> pei, depth; vector<set<int>> tree;
    NetworkSimplex(int n) : n(n), B(n + 1), P(n + 1), pei(n + 1, -1), depth(n + 1), tree(n + 1) {}
    int ae(int a, int b, ll l, ll u, ll c) {
        e.push_back({a, b, 0, u - l, c}); e.push_back({b, a, 0, 0, -c});
        delta += (i128)l * c; B[b] += l, B[a] -= l;
        return sz(e) - 2;
    }
    int addEdge(int u, int v, ll lo, ll hi, ll cost) { lower.push_back(lo); return ae(u, v, lo, hi, cost) / 2; }
    void addSupply(int v, ll b) { B[v] += b; }
    void upd(int ei) { P[e[ei].dst] = P[e[ei].src] + e[ei].cost; pei[e[ei].dst] = ei ^ 1; depth[e[ei].dst] = 1 + depth[e[ei].src]; dfs(e[ei].dst); }
    void dfs(int v) { for (int ei : tree[v]) if (ei != pei[v]) upd(ei); }
    template <class F> void walk(int ei, F f) {               // ei, then the tree path closing its cycle
        f(ei);
        for (int a = e[ei].src, b = e[ei].dst; a != b;) {
            if (depth[a] > depth[b]) f(pei[a] ^ 1), a = e[pei[a]].dst;
            else f(pei[b]), b = e[pei[b]].dst;
        }
    }
    optional<i128> solve() {
        int m = sz(e);
        for (int i = 0; i < m; i += 2) INF += abs(e[i].cost);
        for (int i = 0; i < n; i++) {
            int a = n, b = i; ll c = B[i]; if (c < 0) c = -c, swap(a, b);
            int ei = ae(a, b, 0, c, -INF); tree[a].insert(ei), tree[b].insert(ei ^ 1);
        }
        dfs(n);
        i128 ans = delta; int ptr = 0; const int BLOCK = n / 3 + 1;
        for (int z = 0; z < sz(e) / BLOCK + 1; z++) {
            pair<ll, int> pin{0, -1};
            for (int t = 0; t < BLOCK; ++t, (++ptr) %= sz(e))
                if (e[ptr].flow < e[ptr].cap) pin = min(pin, make_pair(e[ptr].cost + P[e[ptr].src] - P[e[ptr].dst], ptr));
            auto [cost, ein] = pin;
            if (cost == 0) continue;
            pair<ll, int> pout{e[ein].cap - e[ein].flow, ein};
            walk(ein, [&](int ei) { pout = min(pout, make_pair(e[ei].cap - e[ei].flow, ei)); });
            auto [fl, eout] = pout;
            walk(ein, [&](int ei) { e[ei].flow += fl, e[ei ^ 1].flow -= fl; });
            tree[e[ein].src].insert(ein), tree[e[ein].dst].insert(ein ^ 1);
            tree[e[eout].src].erase(eout), tree[e[eout].dst].erase(eout ^ 1);
            upd(pei[e[eout].src] == eout ? ein : ein ^ 1);
            ans += (i128)fl * cost; z = -1;
        }
        for (int i = 0; i < n; i++) {
            if (e[m + 2 * i].flow < e[m + 2 * i].cap) return nullopt;
            ans += (i128)e[m + 2 * i].flow * INF;
        }
        return ans;
    }
    ll flowOn(int id) const { return lower[id] + e[2 * id].flow; }
    ll potential(int v) const { return P[v]; }
};

Cost scaling

Goldberg–Tarjan: costs are multiplied by , and an -optimal flow (all residual reduced costs ) is refined while is divided by 4; after scaling means exactly optimal. First a push–relabel max flow, then the refinement optimises the circulation part too, so negative cycles are fine. Keep well inside 64 bits.

struct CostScaling {                                          // min-cost max-flow, any integer costs
    struct E { int to, rev; ll c, f; };                       // f = residual capacity, c = cost * n
    int N, S, T; ll eps = 0; vector<vector<E>> G;
    vector<unsigned> isq, cur; vector<ll> ex, h; vector<vector<int>> hs; vector<int> co;
    CostScaling(int n, int s, int t) : N(n), S(s), T(t), G(n) {}
    void addEdge(int a, int b, ll cap, ll cost) {
        if (a == b) return;                                   // a non-negative self-loop never helps
        cost *= N; eps = max(eps, abs(cost));
        G[a].push_back({b, sz(G[b]), cost, cap}); G[b].push_back({a, sz(G[a]) - 1, -cost, 0});
    }
    void addFlow(E& e, ll f) {
        E& bk = G[e.to][e.rev];
        if (!ex[e.to] && f) hs[h[e.to]].push_back(e.to);
        e.f -= f; ex[e.to] += f; bk.f += f; ex[bk.to] -= f;
    }
    ll maxFlow() {                                            // highest-label push-relabel
        ex.assign(N, 0); h.assign(N, 0); hs.assign(2 * N, {}); co.assign(2 * N, 0); cur.assign(N, 0);
        h[S] = N; ex[T] = 1; co[0] = N - 1;
        for (auto& e : G[S]) addFlow(e, e.f);
        if (sz(hs[0])) for (int hi = 0; hi >= 0;) {
            int u = hs[hi].back(); hs[hi].pop_back();
            while (ex[u] > 0) {
                if (cur[u] == G[u].size()) {
                    h[u] = 1e9;
                    for (unsigned i = 0; i < G[u].size(); i++) { auto& e = G[u][i]; if (e.f && h[u] > h[e.to] + 1) h[u] = h[e.to] + 1, cur[u] = i; }
                    if (++co[h[u]], !--co[hi] && hi < N) for (int i = 0; i < N; i++) if (hi < h[i] && h[i] < N) --co[h[i]], h[i] = N + 1;
                    hi = h[u];
                } else if (G[u][cur[u]].f && h[u] == h[G[u][cur[u]].to] + 1) addFlow(G[u][cur[u]], min(ex[u], G[u][cur[u]].f));
                else ++cur[u];
            }
            while (hi >= 0 && hs[hi].empty()) --hi;
        }
        return -ex[S];
    }
    void push(E& e, ll amt) { amt = min(amt, e.f); e.f -= amt; ex[e.to] += amt; G[e.to][e.rev].f += amt; ex[G[e.to][e.rev].to] -= amt; }
    void relabel(int v) {
        ll nh = LLONG_MIN / 2;
        for (unsigned i = 0; i < G[v].size(); i++) { const E& e = G[v][i]; if (e.f && nh < h[e.to] - e.c) nh = h[e.to] - e.c, cur[v] = i; }
        h[v] = nh - eps;
    }
    pair<ll, ll> minCostMaxFlow() {
        ll cost = 0;
        for (int i = 0; i < N; i++) for (E& e : G[i]) cost += e.c * e.f;
        ll flow = maxFlow();
        h.assign(N, 0); ex.assign(N, 0); isq.assign(N, 0); cur.assign(N, 0); queue<int> q;
        for (; eps; eps >>= 2) {
            fill(all(cur), 0);
            for (int i = 0; i < N; i++) for (auto& e : G[i]) if (h[i] + e.c - h[e.to] < 0 && e.f) push(e, e.f);
            for (int i = 0; i < N; i++) if (ex[i] > 0) q.push(i), isq[i] = 1;
            while (sz(q)) {
                int u = q.front(); q.pop(); isq[u] = 0;
                while (ex[u] > 0) {
                    if (cur[u] == G[u].size()) relabel(u);
                    for (unsigned& i = cur[u], mx = G[u].size(); i < mx; i++) {
                        E& e = G[u][i];
                        if (h[u] + e.c - h[e.to] < 0) {
                            push(e, ex[u]);
                            if (ex[e.to] > 0 && !isq[e.to]) q.push(e.to), isq[e.to] = 1;
                            if (!ex[u]) break;
                        }
                    }
                }
            }
            if (eps > 1 && eps >> 2 == 0) eps = 1 << 2;
        }
        for (int i = 0; i < N; i++) for (E& e : G[i]) cost -= e.c * e.f;
        return {flow, cost / 2 / N};
    }
    ll flowOn(const E& e) const { return G[e.to][e.rev].f; }
};