DSA DSA In Depth
0%
lesson mcmfmin-cost-flowjohnson-potentials

Minimum-Cost Maximum Flow (MCMF): Successive Shortest Paths with Johnson Potentials

7 min read Module 5.3: Network Flow, Min-Cut & Bipartite Matching Updated Oct 23, 2026
📄 Cheat Sheet
Minimum-Cost Network Flow & Successive Shortest Augmenting Paths
NPTEL (IIT Kharagpur) Minimum-Cost Network Flow & Successive Shortest Augmenting Paths Watch on YouTube ↗

Explains residual edge cost skew-symmetry (-cost on reverse arcs) and why Johnson node potentials let Dijkstra replace Bellman-Ford.

0:00 / --:--

Cost Skew-Symmetry, Negative Cycle Optimality & Successive Shortest Paths

For each original edge $e = (u, v)$ with capacity $c(e)$ and unit cost $w(e)$, its reverse residual partner $e^R = (v, u)$ has initial capacity $0$ and unit cost $w(e^R) = -w(e)$.

Formal Theorem — Min-Cost Flow Optimality & Potential Preservation

Theorem 1 (Negative-Cost Cycle Optimality Criterion): A flow $f$ of value $|f|$ has minimum cost among all flows of value $|f|$ if and only if the residual graph $G_f$ contains no directed cycle of strictly negative total cost.

Theorem 2 (Successive Shortest Path Invariant): Suppose $f$ has no negative-cost residual cycles and $\pi: V \to \mathbb{R}$ are valid node potentials satisfying $w_\pi(u, v) = w(u, v) + \pi(u) - \pi(v) \ge 0$ for all $(u, v) \in E_f$. Let $d'(u)$ be the shortest path distance from $s$ to $u$ in $G_f$ under $w_\pi$, and augment flow along a shortest $s \leadsto t$ path $P$. Then updating potentials to:

$$\pi_{\text{new}}(u) = \pi(u) + d'(u)$$

guarantees $w_{\pi_{\text{new}}}(u, v) \ge 0$ for all edges in the new residual graph $G_{f'}$—including newly created reverse edges $(v, u)$ along $P$, because along $P$, $d'(v) = d'(u) + w_\pi(u, v) \implies w_{\pi_{\text{new}}}(v, u) = 0$! $\blacksquare$

flowchart TD Init["1. Init potentials pi[u]: 0 if all w(e) >= 0, else 1 pass of Bellman-Ford/DAG DP"] --> Dijk["2. Run Dijkstra on G_f using reduced cost w_pi(u,v) = w(u,v) + pi[u] - pi[v] >= 0"] Dijk --> Reach{"Did Dijkstra reach t?"} Reach -- "No" --> Done["Return {totalFlow, totalCost}"] Reach -- "Yes" --> Shift["3. Update pi[u] += d[u] for all reachable u"] Shift --> Aug["4. Find bottleneck Delta on shortest path P; update residual caps and totalCost += Delta * (pi[t] - pi[s])"] Aug --> Dijk
Min-Cut Project Selection Closure, Hungarian (u_i + v_j = c_ij) & Min-Cost Max-Flow
O(V³) Hungarian · O(F·E log V) MCMF

Model Max-Weight Closure (Project Selection) as S-T Min-Cut with ∞ prerequisite arcs, tighten Hungarian dual potentials u_i + v_j = c_ij on the equality subgraph, or route Min-Cost Max-Flow via Johnson reduced costs.

Step 1 / 7
Custom Input:
Timeline

Invariant & State

Max-Weight Closure Theorem: Prerequisite arcs (P1→D1, P2→D2) have capacity ∞ so no finite S-T cut can sever them! Min-Cut = 5 (pay D1) + 8 (drop P2) = 13 ⇒ Max Net Profit = (12+8) - 13 = +7 (select {P1, D1})!
cap=12 (+rev) cap=8 (+rev) cap=∞ (prereq) cap=∞ (prereq) cap=5 (cost) cap=11 (cost) S Source P1 (+12) IN S (Keep!) P2 (+8) IN T (Drop) D1 (-5) IN S (Pay 5) D2 (-11) IN T (Skip) T Sink
Gross Positive Sum W⁺
12 + 8 = 20
S-T Min-Cut Capacity
5 (buy D1) + 8 (forgo P2) = 13
Optimal Net Profit
W⁺ - MinCut = 20 - 13 = +7
[Step 1] S-T Min-Cut capacity 13 selects closed project set {P1, D1} for optimal profit 12 - 5 = +7.
Hardware Reality — True Path Cost from Potential Difference $\pi[t] - \pi[s]$

After updating $\pi[u] \mathrel{+}= d[u]$ (where $\pi[s] = 0$), what is the true unadjusted unit cost of the augmenting path $P$ from $s$ to $t$? By telescoping sum, $\sum_{e \in P} w(e) = \pi_{\text{new}}[t] - \pi_{\text{new}}[s] = \pi_{\text{new}}[t]$! You don't even need to sum edge costs along the path—simply add pushed * pi[t] to totalCost!

MCMF AlgorithmTime ComplexityHandles Initial $w(e) < 0$?Best Use Case
Successive Shortest Path (SPFA)$O(|f^*| \cdot V E)$Yes (if no negative cycles)Small flow $|f^*|$, general initial costs
SSP + Johnson Potentials + Dijkstra$O(V E + |f^*| \cdot E \log V)$Yes (via 1 initial Bellman-Ford)Standard production & CP choice
Primal-Dual Capacity Scaling$O(E^2 \log U \log V)$YesLarge capacities $U \gg V$

Production C++20 Implementation: Min-Cost Max-Flow with Johnson Potentials & Dijkstra

#include <vector>
#include <queue>
#include <algorithm>
#include <limits>

class MinCostMaxFlow {
public:
    struct Arc {
        int from, to;
        long long cap, cost;
    };

    explicit MinCostMaxFlow(int n) : n(n), adj(n), pi(n, 0) {}

    void addEdge(int u, int v, long long cap, long long cost) {
        adj[u].push_back(static_cast<int>(arcs.size()));
        arcs.push_back({u, v, cap, cost});
        adj[v].push_back(static_cast<int>(arcs.size()));
        arcs.push_back({v, u, 0, -cost}); // Reverse edge has negative cost!
        if (cost < 0) hasNegativeInitCost = true;
    }

    // Optional: call or auto-triggered if initial graph has negative-cost edges (no negative cycles)
    void initPotentialsBellmanFord(int s) {
        std::fill(pi.begin(), pi.end(), INF);
        pi[s] = 0;
        for (int iter = 0; iter < n - 1; ++iter) {
            bool changed = false;
            for (const auto& e : arcs) {
                if (e.cap > 0 && pi[e.from] < INF && pi[e.from] + e.cost < pi[e.to]) {
                    pi[e.to] = pi[e.from] + e.cost;
                    changed = true;
                }
            }
            if (!changed) break;
        }
    }

    std::pair<long long, long long> minCostMaxFlow(int s, int t, long long flowLimit = INF) {
        if (hasNegativeInitCost) initPotentialsBellmanFord(s);

        long long totalFlow = 0, totalCost = 0;
        std::vector<long long> dist(n);
        std::vector<int> parentArc(n);

        while (totalFlow < flowLimit) {
            std::fill(dist.begin(), dist.end(), INF);
            std::fill(parentArc.begin(), parentArc.end(), -1);
            using State = std::pair<long long, int>;
            std::priority_queue<State, std::vector<State>, std::greater<State>> pq;

            dist[s] = 0;
            pq.emplace(0, s);

            while (!pq.empty()) {
                auto [d, u] = pq.top();
                pq.pop();
                if (d > dist[u]) continue;

                for (int id : adj[u]) {
                    const auto& e = arcs[id];
                    if (e.cap == 0) continue;
                    long long reducedCost = e.cost + pi[u] - pi[e.to];
                    if (d + reducedCost < dist[e.to]) {
                        dist[e.to] = d + reducedCost;
                        parentArc[e.to] = id;
                        pq.emplace(dist[e.to], e.to);
                    }
                }
            }

            if (dist[t] == INF) break;

            for (int i = 0; i < n; ++i) {
                if (dist[i] < INF) pi[i] += dist[i];
            }

            long long push = flowLimit - totalFlow;
            for (int cur = t; cur != s; cur = arcs[parentArc[cur]].from) {
                push = std::min(push, arcs[parentArc[cur]].cap);
            }
            for (int cur = t; cur != s; cur = arcs[parentArc[cur]].from) {
                int id = parentArc[cur];
                arcs[id].cap -= push;
                arcs[id ^ 1].cap += push;
            }

            totalFlow += push;
            totalCost += push * (pi[t] - pi[s]);
        }
        return {totalFlow, totalCost};
    }

private:
    static constexpr long long INF = std::numeric_limits<long long>::max() / 4;
    int n;
    bool hasNegativeInitCost = false;
    std::vector<Arc> arcs;
    std::vector<std::vector<int>> adj;
    std::vector<long long> pi;
};
Failure Mode — Updating Potentials Only Along the Augmenting Path

You must update $\pi[u] \mathrel{+}= \text{dist}[u]$ for every reachable vertex $u$ (if (dist[u] < INF)), not just vertices on the chosen $s \leadsto t$ path! If you only update $\pi$ on the path, non-path edges leaving the path will violate $w_\pi(u, v) \ge 0$ in the next iteration and corrupt Dijkstra.

Check yourself Question 1 of 4
Concept Check Cost of Reverse Residual Edges in MCMF recall
Section: Reverse Edge Cost

In Minimum-Cost Maximum Flow, if a forward directed edge $e = (u, v)$ has capacity $c$ and unit cost $+w$, what are the initial capacity and unit cost of its reverse residual edge $e^R = (v, u)$?

⚡ Interactive Challenge · Staff Level

Algorithmic Design & Complexity Check

Analyze the invariant, asymptotic bound, and hardware behavior before selecting.

Let $C(F)$ denote the minimum cost to send $F$ units of flow from $s$ to $t$. What mathematical property does the function $F \mapsto C(F)$ satisfy?

Key Takeaways

Every forward edge of cost +w induces a backward residual edge of cost -w, so residual networks naturally acquire negative-cost edges.
Maintaining Johnson potentials pi[u] += dist[u] across all reachable vertices keeps every residual edge's reduced cost w_pi(u,v) = w(u,v) + pi[u] - pi[v] >= 0.
When initial edge costs are non-negative, pi[u] can be initialized to 0; if initial negative costs exist (without negative cycles), one Bellman-Ford pass initializes pi[u].
The true unadjusted unit cost of the shortest augmenting path equals pi_new[t] - pi_new[s].
Was this lesson helpful? Spot an issue or have a suggestion? Let us know on GitHub.
Report an issue