Minimum Cost Maximum Flow
Key Points
- •Minimum Cost Maximum Flow (MCMF) finds the maximum possible flow from a source to a sink while minimizing the total cost paid per unit of flow along edges.
- •It repeatedly sends flow along the currently cheapest augmenting path in the residual graph (successive shortest augmenting path).
- •Using potentials (Johnson’s reweighting) lets us run Dijkstra on non-negative reduced costs, yielding fast practical performance: typically O(F E log V).
- •SPFA can handle negative costs directly but is slower in worst case: O(F V E), though it is simple and fine for small graphs.
- •The assignment/transportation problems are special cases reducible to MCMF by building a bipartite flow network with unit capacities.
- •Negative cost edges are safe if you initialize node potentials with Bellman–Ford (or SPFA) before running Dijkstra on reduced costs.
- •Always add reverse edges with negative costs to form the residual graph; forgetting this breaks correctness and prevents canceling expensive choices.
- •Use 64-bit (long long) for costs to avoid overflow when summing many edge costs or multiplying flow by cost.
Prerequisites
- →Graph representations (adjacency lists) — MCMF is implemented on directed graphs with forward and reverse edges; you must understand how to store and traverse edges efficiently.
- →Max flow and residual graphs — MCMF extends max flow; residual capacities and reverse edges are essential for augmenting and canceling flow.
- →Shortest path algorithms (Dijkstra, Bellman–Ford, SPFA) — Successive shortest augmenting path requires computing cheapest paths, often under constraints like non-negative weights.
- →Priority queues / heaps — Dijkstra’s algorithm depends on a min-priority queue for efficiency.
- →Greedy algorithms and invariants — Understanding why picking the current cheapest path remains globally correct relies on maintaining reduced costs and potentials.
- →Basic linear programming duality (optional) — Potentials correspond to dual variables; this perspective explains optimality and the non-existence of negative cycles.
- →Integer arithmetic and overflow handling — Costs and distances can grow large; using 64-bit integers and safe infinities prevents bugs.
Detailed Explanation
Tap terms for definitions01Overview
Minimum Cost Maximum Flow (MCMF) is a network flow algorithm that chooses not only how much flow to send from a source to a sink, but also which paths to use so that the total monetary (or other) cost is as small as possible. Each directed edge has a capacity (how much it can carry) and a per-unit cost (how expensive it is to use). The algorithm’s classic approach is the successive shortest augmenting path method: repeatedly find the cheapest s→t path in the current residual network and push as much flow as possible along it, updating residual capacities and reverse edges. This process continues until no more augmenting path exists (max flow reached) or a flow limit is met. Although finding a shortest path may be complicated by negative edge costs, a standard trick—node potentials (Johnson’s reweighting)—keeps reduced costs non-negative so Dijkstra’s algorithm can be used efficiently. Alternative strategies include SPFA-based shortest paths, cycle-canceling (find and eliminate negative-cost cycles), and scaling methods. MCMF is foundational for problems like assignment, transportation, min-cost k paths, and various scheduling or resource allocation tasks. Conceptually, MCMF unifies shortest paths and max-flow: it maximizes quantity while optimizing quality (cost) of the routing.
02Intuition & Analogies
Imagine shipping packages from a warehouse (source) to customers (sink) through a road network. Each road can carry only so many trucks per day (capacity), and each trip on that road costs fuel/toll money (cost per unit). If you only maximize the number of packages (max flow), you might take very long or expensive detours. If you only minimize cost for a single package (shortest path), you may ignore the need to serve everyone. Minimum Cost Maximum Flow balances both: it tries to move as many packages as possible while choosing routes that, on average, keep expenses down. Think of it like repeatedly picking the “cheapest viable route” given current congestion. After you send some trucks, roads fill up and the remaining best route may change. The residual network captures these dynamics: it not only shows remaining free lanes (forward residual capacity) but also allows “undoing” part of a previous choice (reverse edges with negative cost) if a new combination is globally cheaper—like rerouting some trucks to open a better plan. Potentials are mental price stickers on cities: when adjusted correctly, all adjusted road prices become non-negative, letting us safely use Dijkstra (which expects no negative edges) to quickly find the next cheapest route. Over time, this greedy yet globally aware process builds a shipping plan that moves the maximum number of packages for the lowest total bill.
03Formal Definition
04When to Use
Use MCMF whenever you must route quantity under capacities but every unit has a price you want to minimize. Classic applications include: (1) Assignment problem: match n workers to n jobs with minimum total cost; build a bipartite flow network with unit capacities and edge costs equal to assignment costs. (2) Transportation/supply-demand: ship goods from multiple suppliers to consumers with different per-route shipping costs; attach a super-source/sink and connect supplies/demands. (3) Min-cost k disjoint (or capacity-limited) paths: send exactly k units of flow from s to t with minimum total path cost. (4) Project selection and scheduling with penalties/bonuses modeled as costs along edges. (5) Network design with per-edge usage fees where you also have capacity limits. Choose SPFA-based MCMF for small inputs or when implementation simplicity matters and performance is not critical. For larger graphs with possibly negative edges, use potentials with an initial Bellman–Ford (or SPFA) followed by repeated Dijkstra; this is the go-to for competitive programming in the 2000–2400 range. Consider specialized solvers (Hungarian algorithm) when the graph is dense and exactly matches the assignment structure for best asymptotic performance.
⚠️Common Mistakes
- Forgetting reverse edges: MCMF relies on residual reverse edges with negative costs to allow canceling or rerouting previous choices. Always add a reverse edge for every forward edge with capacity 0 and cost = -cost.
- Using Dijkstra directly on negative edges: If any residual edge can have negative cost, run Bellman–Ford (or SPFA) once to initialize potentials, then use Dijkstra on non-negative reduced costs.
- Integer overflow: Total cost can exceed 32-bit range when summing many edges or multiplying by large flows. Store costs, distances, and total cost in 64-bit (long long).
- Wrong infinity and distance updates: Use a safely large INF (e.g., LLONG_MAX/4) and guard against addition overflow when relaxing edges.
- Not updating potentials correctly: After each Dijkstra, set \pi(v) := \pi(v) + dist(v) for all reachable v; leave others unchanged. This maintains non-negative reduced costs.
- Mishandling flow limit vs. maximum flow: If you need exactly k units, stop when the sent flow reaches k; if you need max flow, keep augmenting until no path exists.
- Building the assignment network incorrectly: Remember S→left nodes (cap=1), left→right edges (cap=1, cost=matrix[i][j]), right→T (cap=1). Off-by-one errors and index swaps are common.
- Performance pitfalls: SPFA can be very slow on adversarial inputs; for larger constraints, prefer potentials + Dijkstra. Avoid O(VE) scans by using adjacency lists and priority queues.
Key Formulas
Total cost of a flow
Explanation: The total cost equals the sum over all edges of (flow on the edge) times (per-unit cost). This is the quantity MCMF tries to minimize among maximum flows.
Capacity constraints
Explanation: Flow on each edge cannot be negative and cannot exceed its capacity. These constraints define feasibility.
Flow conservation
Explanation: Except at source and sink, the amount of flow entering a node equals the amount leaving it. This preserves continuity of flow.
Reduced cost (Johnson reweighting)
Explanation: By assigning potentials to nodes, we transform edge costs to reduced costs. If all reduced costs in the residual graph are non-negative, we can safely run Dijkstra.
Potential update after Dijkstra
Explanation: After computing shortest distances with reduced costs, add these distances to the potentials. This maintains non-negative reduced costs in the next iteration.
SPFA-based complexity
Explanation: Each augmentation may take O(VE) in the worst case to find a shortest path, and we may do this up to F times, where F is the total flow sent.
Dijkstra with potentials complexity
Explanation: With non-negative reduced costs, each shortest path is found by Dijkstra in O(E log V) time (using a binary heap). This repeats up to F augmentations.
Negative-cycle optimality condition
Explanation: A flow is minimum cost for its value if and only if there are no negative-cost cycles in the residual graph. If one exists, pushing flow around it reduces total cost.
Linear programming form (primal)
Explanation: MCMF can be written as an LP: minimize linear cost subject to flow conservation (Af=b) and capacity bounds. Dual variables correspond to node potentials.
Summation identity (reference)
Explanation: Commonly used to estimate the number of relaxations or bounds in analyses; for example, repeated improvements that decrease a potential by at least 1 per step.
Complexity Analysis
Code Examples
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 struct MinCostMaxFlow { 5 struct Edge { 6 int to, rev; 7 long long cap, cost; 8 }; 9 10 int n; 11 vector<vector<Edge>> g; 12 13 MinCostMaxFlow(int n): n(n), g(n) {} 14 15 void addEdge(int u, int v, long long cap, long long cost) { 16 Edge a{v, (int)g[v].size(), cap, cost}; 17 Edge b{u, (int)g[u].size(), 0, -cost}; // reverse edge 18 g[u].push_back(a); 19 g[v].push_back(b); 20 } 21 22 pair<long long,long long> minCostMaxFlow(int s, int t, long long max_flow = (1LL<<60)) { 23 const long long INF = (1LL<<60); 24 long long flow = 0, cost = 0; 25 vector<long long> pot(n, 0); // node potentials 26 vector<long long> dist(n); 27 vector<int> pv(n), pe(n); // parent vertex and edge index 28 29 // If there are negative edge costs, initialize potentials by Bellman-Ford from s 30 // to ensure non-negative reduced costs for reachable nodes. 31 // This is optional if all costs >= 0. 32 { 33 vector<long long> d(n, INF); 34 d[s] = 0; 35 bool any = true; 36 for (int it = 0; it < n - 1 && any; ++it) { 37 any = false; 38 for (int u = 0; u < n; ++u) if (d[u] < INF) { 39 for (auto &e : g[u]) if (e.cap > 0) { 40 if (d[e.to] > d[u] + e.cost) { 41 d[e.to] = d[u] + e.cost; 42 any = true; 43 } 44 } 45 } 46 } 47 for (int v = 0; v < n; ++v) if (d[v] < INF) pot[v] = d[v]; 48 } 49 50 while (flow < max_flow) { 51 // Dijkstra on reduced costs: w'(u,v) = cost + pot[u] - pot[v] >= 0 52 priority_queue<pair<long long,int>, vector<pair<long long,int>>, greater<pair<long long,int>>> pq; 53 fill(dist.begin(), dist.end(), INF); 54 dist[s] = 0; 55 pq.push({0, s}); 56 while (!pq.empty()) { 57 auto [du, u] = pq.top(); pq.pop(); 58 if (du != dist[u]) continue; 59 for (int i = 0; i < (int)g[u].size(); ++i) { 60 auto &e = g[u][i]; 61 if (e.cap <= 0) continue; 62 long long nd = du + e.cost + pot[u] - pot[e.to]; 63 if (nd < dist[e.to]) { 64 dist[e.to] = nd; 65 pv[e.to] = u; pe[e.to] = i; 66 pq.push({nd, e.to}); 67 } 68 } 69 } 70 if (dist[t] == INF) break; // no more augmenting path -> max flow reached 71 72 // Update potentials: pi(v) += dist(v) for visited nodes 73 for (int v = 0; v < n; ++v) if (dist[v] < INF) pot[v] += dist[v]; 74 75 // Find bottleneck capacity on the path 76 long long add = max_flow - flow; 77 for (int v = t; v != s; v = pv[v]) { 78 auto &e = g[pv[v]][pe[v]]; 79 add = min(add, e.cap); 80 } 81 82 // Augment and accumulate real (original) cost 83 for (int v = t; v != s; v = pv[v]) { 84 auto &e = g[pv[v]][pe[v]]; 85 auto &er = g[v][e.rev]; 86 e.cap -= add; 87 er.cap += add; 88 cost += add * e.cost; // original cost (not reduced) 89 } 90 flow += add; 91 } 92 return {flow, cost}; 93 } 94 }; 95 96 int main() { 97 ios::sync_with_stdio(false); 98 cin.tie(nullptr); 99 100 // Example graph demonstrating negative edges handled via potentials 101 // Nodes: 0 (s), 3 (t) 102 int n = 4; 103 MinCostMaxFlow mcmf(n); 104 105 mcmf.addEdge(0, 1, 2, 1); // s->1 106 mcmf.addEdge(0, 2, 1, 5); // s->2 107 mcmf.addEdge(1, 2, 1, -2); // negative cost edge 108 mcmf.addEdge(1, 3, 1, 2); // 1->t 109 mcmf.addEdge(2, 3, 2, 1); // 2->t 110 111 auto [flow, cost] = mcmf.minCostMaxFlow(0, 3); 112 cout << "Max flow: " << flow << "\n"; 113 cout << "Min total cost: " << cost << "\n"; 114 115 // Expected: flow = 3, cost = 9 (paths: 0-1-2-3 cost 0, 0-1-3 cost 3, 0-2-3 cost 6) 116 return 0; 117 } 118
This implementation uses successive shortest augmenting paths with Johnson potentials. We optionally run Bellman–Ford once to handle negative original costs. Each iteration runs Dijkstra on non-negative reduced costs to find the cheapest residual path, augments along it, and updates potentials to keep reduced costs non-negative. The example demonstrates correctness with a negative-cost edge and prints the resulting max flow and minimum total cost.
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 struct MinCostMaxFlowSPFA { 5 struct Edge { int to, rev; int cap; long long cost; }; 6 int n; vector<vector<Edge>> g; 7 MinCostMaxFlowSPFA(int n): n(n), g(n) {} 8 9 void addEdge(int u, int v, int cap, long long cost) { 10 Edge a{v, (int)g[v].size(), cap, cost}; 11 Edge b{u, (int)g[u].size(), 0, -cost}; 12 g[u].push_back(a); g[v].push_back(b); 13 } 14 15 pair<long long,long long> minCostMaxFlow(int s, int t, long long max_flow = (1LL<<60)) { 16 const long long INF = (1LL<<60); 17 long long flow = 0, cost = 0; 18 vector<long long> dist(n); 19 vector<int> inq(n), pv(n), pe(n); 20 21 while (flow < max_flow) { 22 fill(dist.begin(), dist.end(), INF); 23 fill(inq.begin(), inq.end(), 0); 24 queue<int> q; 25 dist[s] = 0; q.push(s); inq[s] = 1; 26 while (!q.empty()) { 27 int u = q.front(); q.pop(); inq[u] = 0; 28 for (int i = 0; i < (int)g[u].size(); ++i) { 29 auto &e = g[u][i]; 30 if (e.cap <= 0) continue; 31 if (dist[e.to] > dist[u] + e.cost) { 32 dist[e.to] = dist[u] + e.cost; 33 pv[e.to] = u; pe[e.to] = i; 34 if (!inq[e.to]) { q.push(e.to); inq[e.to] = 1; } 35 } 36 } 37 } 38 if (dist[t] == INF) break; // no path 39 40 // Bottleneck 41 int add = (int)min<long long>(max_flow - flow, INT_MAX); 42 for (int v = t; v != s; v = pv[v]) add = min(add, g[pv[v]][pe[v]].cap); 43 44 for (int v = t; v != s; v = pv[v]) { 45 auto &e = g[pv[v]][pe[v]]; 46 auto &er = g[v][e.rev]; 47 e.cap -= add; er.cap += add; cost += 1LL * add * e.cost; 48 } 49 flow += add; 50 } 51 return {flow, cost}; 52 } 53 }; 54 55 int main(){ 56 ios::sync_with_stdio(false); 57 cin.tie(nullptr); 58 59 int n = 4; // 0(s), 3(t) 60 MinCostMaxFlowSPFA mcmf(n); 61 62 mcmf.addEdge(0, 1, 2, 1); 63 mcmf.addEdge(0, 2, 1, 5); 64 mcmf.addEdge(1, 2, 1, -2); 65 mcmf.addEdge(1, 3, 1, 2); 66 mcmf.addEdge(2, 3, 2, 1); 67 68 auto [flow, cost] = mcmf.minCostMaxFlow(0, 3); 69 cout << flow << " " << cost << "\n"; // 3 9 70 return 0; 71 } 72
This is a compact SPFA-based MCMF. It directly supports negative edge costs without potentials. It is easy to code and debug, suitable for small to medium inputs, but can be slow on worst-case graphs.
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 struct MinCostMaxFlow { 5 struct Edge { int to, rev; int cap; long long cost; }; 6 int n; vector<vector<Edge>> g; 7 MinCostMaxFlow(int n): n(n), g(n) {} 8 void addEdge(int u, int v, int cap, long long cost){ 9 Edge a{v, (int)g[v].size(), cap, cost}; 10 Edge b{u, (int)g[u].size(), 0, -cost}; 11 g[u].push_back(a); g[v].push_back(b); 12 } 13 pair<long long,long long> minCostMaxFlow(int s, int t, int req_flow){ 14 const long long INF = (1LL<<60); 15 long long flow = 0, cost = 0; 16 vector<long long> pot(n,0), dist(n); 17 vector<int> pv(n), pe(n); 18 // No negative edges here; pot=0 is fine. If negative, run Bellman-Ford. 19 while (flow < req_flow) { 20 fill(dist.begin(), dist.end(), INF); 21 dist[s] = 0; 22 priority_queue<pair<long long,int>, vector<pair<long long,int>>, greater<pair<long long,int>>> pq; 23 pq.push({0,s}); 24 while(!pq.empty()){ 25 auto [du,u]=pq.top(); pq.pop(); if(du!=dist[u]) continue; 26 for(int i=0;i<(int)g[u].size();++i){ auto &e=g[u][i]; if(e.cap<=0) continue; 27 long long nd=du + e.cost + pot[u] - pot[e.to]; 28 if(nd<dist[e.to]){ dist[e.to]=nd; pv[e.to]=u; pe[e.to]=i; pq.push({nd,e.to}); } 29 } 30 } 31 if(dist[t]==INF) break; // cannot send required flow 32 for(int v=0; v<n; ++v) if(dist[v]<INF) pot[v]+=dist[v]; 33 int add = req_flow - (int)flow; 34 for(int v=t; v!=s; v=pv[v]) add=min(add, g[pv[v]][pe[v]].cap); 35 for(int v=t; v!=s; v=pv[v]){ 36 auto &e=g[pv[v]][pe[v]]; auto &er=g[v][e.rev]; 37 e.cap-=add; er.cap+=add; cost += 1LL*add*e.cost; 38 } 39 flow += add; 40 } 41 return {flow, cost}; 42 } 43 }; 44 45 int main(){ 46 ios::sync_with_stdio(false); 47 cin.tie(nullptr); 48 49 // Assignment example: 3 workers, 3 jobs 50 // Cost matrix C[i][j] 51 vector<vector<int>> C = { 52 {4,1,3}, 53 {2,0,5}, 54 {3,2,2} 55 }; 56 int n = (int)C.size(); 57 int S = 2*n, T = 2*n+1; 58 MinCostMaxFlow mcmf(2*n+2); 59 60 // S -> workers 61 for(int i=0;i<n;++i) mcmf.addEdge(S, i, 1, 0); 62 // workers -> jobs with costs 63 for(int i=0;i<n;++i){ 64 for(int j=0;j<n;++j){ 65 mcmf.addEdge(i, n+j, 1, C[i][j]); 66 } 67 } 68 // jobs -> T 69 for(int j=0;j<n;++j) mcmf.addEdge(n+j, T, 1, 0); 70 71 auto [flow, cost] = mcmf.minCostMaxFlow(S, T, n); 72 cout << "Assigned pairs: " << flow << "\n"; 73 cout << "Minimum total cost: " << cost << "\n"; // Expected 5 74 75 // Optional: recover which worker->job edges are used by checking reverse edges with cap>0 76 cout << "Assignments (worker -> job):\n"; 77 for(int i=0;i<n;++i){ 78 for(auto &e : mcmf.g[i]){ 79 if(e.to>=n && e.to<2*n){ // edge to job node 80 int j = e.to - n; 81 // If reverse edge (job->worker) has positive cap, this i->j was used 82 const auto &rev = mcmf.g[e.to][e.rev]; 83 if(rev.cap > 0) cout << i << " -> " << j << "\n"; 84 } 85 } 86 } 87 return 0; 88 } 89
We reduce the 3×3 assignment problem to MCMF with a bipartite graph: source to workers, workers to jobs (edge cost = assignment cost), jobs to sink. Each edge has capacity 1, and we send exactly n units of flow. The printed cost (5) matches the known optimum for the sample matrix.