Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Network Flow: Advanced Techniques

Building on max-flow fundamentals and Dinic/Push-Relabel, this file covers the frontier of network flow algorithms: min-cost max-flow with advanced implementations, cost-scaling methods, push-relabel optimizations, and Dinic variants with dynamic trees.


Min-Cost Max-Flow: Beyond Successive Shortest Paths

Problem Statement

Given a flow network $G = (V, E)$ with capacities $c(e) \geq 0$ and costs $w(e)$ per unit flow, find a maximum flow of minimum total cost $\sum_e f(e) \cdot w(e)$. This generalizes both max-flow (set all costs to 0) and shortest path (set all capacities to 1).

Successive Shortest Path (SSP) Recap

The standard approach augments along shortest (minimum-cost) paths in the residual graph using Bellman-Ford or Dijkstra with potentials. Each augmentation preserves the optimality of potentials (reduced costs remain non-negative).

Complexity: $O(F \cdot E \log V)$ with Dijkstra + potentials, where $F$ is the max-flow value. This is pseudo-polynomial and degrades when capacities are large.

Capacity Scaling for MCMF

Idea: Instead of augmenting one shortest path at a time (which may push tiny amounts), use capacity scaling. At scale $\Delta$, only consider edges with residual capacity $\geq \Delta$. Find augmenting paths among these “thick” edges, halving $\Delta$ when no more exist.

function min_cost_max_flow_capacity_scaling(G, s, t):
    Delta = 2^floor(log2(max_capacity))
    while Delta >= 1:
        while shortest_augmenting_path_exists(G, s, t, Delta):
            augment along shortest path (residual cap >= Delta)
        Delta = Delta // 2
    return flow, cost

Complexity: $O(E \log U \cdot (E + V \log V))$ where $U = \max c(e)$. The number of $\Delta$-phases is $O(\log U)$ and each phase runs $O(E)$ Dijkstra calls, but in practice far fewer augmentations occur per phase.

Interview Angle: “How would you improve SSP when edge capacities are in the billions?” Describe capacity scaling. Explain that each $\Delta$-phase is like a “coarse” flow that is progressively refined.

Cost Scaling (Goldberg-Tarjan Style)

Cost scaling works on the push-relabel framework. Instead of scaling capacities, we scale the costs. We maintain that reduced costs are within $[-\epsilon, \infty)$ and gradually shrink $\epsilon$.

Key idea: At each $\epsilon$-scale, we have a valid flow and node potentials such that for every residual edge $(u,v)$, the reduced cost $c_\pi(u,v) = w(u,v) + \pi(u) - \pi(v) \geq -\epsilon$. Edges with $c_\pi < 0$ are admissible and we push flow through them.

function cost_scaling_mcmf(G, s, t):
    epsilon = max_edge_cost
    initialize: zero flow, pi[v] = 0 for all v
    while epsilon >= 1/n:
        # Refine at this epsilon
        while admissible edge exists:
            push excess through admissible edges (push-relabel)
            relabel nodes: pi[v] += epsilon  # makes some edges admissible
        epsilon = epsilon / 2
    return flow, cost

Complexity: $O(V^2 \cdot E \cdot \log(VC))$ where $C = \max |w(e)|$. In practice, cost scaling significantly outperforms SSP on dense, high-capacity instances.

Network Simplex

A primal-dual algorithm based on the simplex method for linear programs. It maintains a spanning tree of the graph where tree edges carry flow and non-tree edges are at their bounds. Each pivot swaps a non-tree edge into the tree.

Strengths: Extremely fast in practice (often 5-10x faster than cost-scaling on real-world road/network instances). Weaknesses: No polynomial worst-case bound (though it rarely cycles in practice with anti-cycling rules).

AlgorithmWorst-Case ComplexityPractical SpeedNotes
SSP + Dijkstra$O(FE \log V)$ModerateSimple, good for sparse/low-flow
SSP + Capacity Scaling$O(E^2 \log U \log V)$GoodBetter for large capacities
Cost Scaling$O(V^2 E \log(VC))$Very GoodBest polynomial bound
Network SimplexExponential (rare)Excellent in practiceIndustry standard (e.g., LEMON, Google OR-Tools)

Push-Relabel: Advanced Optimizations

Global Relabeling

The standard push-relabel uses local relabeling: when a node has excess but no admissible edges, increase its height. Global relabeling periodically recomputes exact distances from $t$ via BFS on the reverse residual graph, resetting all heights at once.

Impact: Reduces the number of relabel operations dramatically. In practice, a global relabel every $O(V)$ push operations gives a ~2-5x speedup.

function push_relabel_with_global_relabel(G, s, t):
    initialize heights via BFS from t on reverse graph
    push_counter = 0
    while excess exists at non-s,t nodes:
        if push_counter % V == 0:
            # Global relabel
            h[v] = dist_from_t[v] for all v  # BFS on G_rev
            h[s] = V  # keep source high
        discharge(v)  # standard push or relabel
        push_counter += 1

Gap Relabeling

If there exists a height $k$ such that no node has height $k$ but nodes have heights $> k$, then all nodes with height $> k$ can be set to $\min(\max(k+1, t\text{-distance}), V+1)$. This “gap” means those nodes can never push flow to $t$.

Complexity benefit: The number of relabel operations drops from $O(V^2)$ to $O(V^2 / \text{gap frequency})$. Combined with global relabeling, this is the fastest known push-relabel variant.

Highest-Label Selection Rule

Instead of FIFO (process nodes in queue order), always discharge the node with the highest height. This is implemented with a bucket queue (array of lists indexed by height).

Complexity: $O(V^2 \sqrt{E})$ — strictly better than FIFO’s $O(V^3)$. The intuition: high nodes have more “pressure” and can push flow further toward the sink.

Two-Level Push-Relabel with Dynamic Trees

Augment push-relabel with a dynamic tree data structure (link-cut trees) that represents the admissible graph (edges with $h(u) = h(v) + 1$). Instead of pushing one unit at a time along a chain, find the bottleneck of the entire path in the tree and push the maximum possible.

Complexity: $O(VE \log(V^2/E))$ — currently the fastest known strongly polynomial max-flow algorithm. The dynamic tree allows $O(\log V)$ per tree-path push instead of $O(\text{path length})$.


Dinic’s Algorithm: Optimizations

Current-Arc Optimization

The single most important Dinic optimization. For each node, maintain a pointer to the next edge to try. Once an edge is saturated or leads to a dead end, advance the pointer past it. This ensures each edge is considered at most once per BFS phase.

// C++ current-arc Dinic
struct Dinic {
    struct Edge { int to, rev; long long cap; };
    vector<vector<Edge>> g;
    vector<int> level, iter;
    
    bool bfs(int s, int t) {
        level.assign(n, -1);
        queue<int> q; q.push(s); level[s] = 0;
        while (!q.empty()) {
            int v = q.front(); q.pop();
            for (auto& e : g[v])
                if (e.cap > 0 && level[e.to] < 0)
                    level[e.to] = level[v] + 1, q.push(e.to);
        }
        return level[t] >= 0;
    }
    
    long long dfs(int v, int t, long long f) {
        if (v == t) return f;
        for (int& i = iter[v]; i < g[v].size(); i++) {  // current-arc
            Edge& e = g[v][i];
            if (e.cap > 0 && level[v] < level[e.to]) {
                long long d = dfs(e.to, t, min(f, e.cap));
                if (d > 0) {
                    e.cap -= d;
                    g[e.to][e.rev].cap += d;
                    return d;
                }
            }
        }
        return 0;
    }
    
    long long max_flow(int s, int t) {
        long long flow = 0;
        while (bfs(s, t)) {
            iter.assign(n, 0);
            long long f;
            while ((f = dfs(s, t, LLONG_MAX)) > 0) flow += f;
        }
        return flow;
    }
};

Multi-Threaded Dinic

Each BFS phase finds a blocking flow. The DFS for finding augmenting paths can be parallelized: each thread picks a different path from $s$ through the level graph, but conflicts on edge capacities require atomic operations or fine-grained locking. In practice, a lock-free approach with CAS on capacities works well when the graph is wide (many edge-disjoint paths).

Dinic on Unit Capacity Graphs

When all capacities are 1 (e.g., bipartite matching), Dinic runs in $O(\min(V^{2/3}, E^{1/2}) \cdot E)$ time. The key insight: each BFS phase increases the distance from $s$ to $t$ by at least 1, and the level graph has a specific structure for unit capacities.

Special case — bipartite matching: $O(E\sqrt{V})$ matching the Hopcroft-Karp bound.


Min-Cost Flow: Applications

Assignment with Complex Costs

Standard assignment: $n$ workers, $n$ tasks, cost matrix. MCMF on a bipartite graph with $O(n^2)$ edges gives optimal assignment in $O(n^3 \log n)$.

Minimum-Cost Circulation

When there is no designated source/sink but the graph has lower bounds $l(e)$ and demands $d(v)$, transform to a standard MCMF by adding a super-source and super-sink.

Convex Cost Flow

When edge costs are convex functions of flow (e.g., $w(f) = af^2 + bf + c$), we can model this by splitting each edge into $O(\log U)$ parallel edges at different cost breakpoints, then running standard MCMF.

Applications in Competitive Programming

  • Minimum mean-weight cycle cancellation: Alternate approach to MCMF; cancel the most negative mean-weight cycle repeatedly.
  • Flow with lower bounds: Model constraints like “at least $k$ units must flow.”
  • Project selection: Maximize profit where projects have costs and dependencies.

Interview Angle: “How would you route $k$ deliveries minimizing total distance, respecting vehicle capacities?” Model as MCMF: each delivery is demand, roads are edges with capacity = road capacity, cost = distance.


Comparison: When to Use What

              | Sparse   | Dense    | Unit Cap | Large Cost
              | (E~V)    | (E~V^2)  |          | Range
--------------|----------|----------|----------|------------
Dinic         | O(EV^0.5)| O(V^2E)  | O(EV^2/3)| N/A
Push-Relabel  | O(V^3)   | O(V^3)   | N/A      | N/A
HL Push-Rel.  | O(V^2E^0.5) best  |          |          |
SSP MCMF      | Fast     | Slow     | N/A      | Bad
Cost Scaling  | Moderate | Good     | N/A      | Excellent
Network Simplex| Excellent | Excellent| N/A      | Excellent

Rule of thumb: For competitive programming, Dinic with current-arc is almost always sufficient for max-flow, and SSP with potentials for MCMF. For production systems with millions of edges, use Network Simplex (OR-Tools, LEMON) or cost-scaling implementations.

Further Reading

  • Goldberg & Tarjan, “A New Approach to the Maximum-Flow Problem” (1988) — the original push-relabel paper
  • Goldberg, “The Partial Augment-Relabel Algorithm for the Minimum-Cost Flow Problem” (2008) — cost scaling
  • Ahuja, Magnanti, Orlin, Network Flows: Theory, Algorithms, and Applications (1993) — comprehensive reference
  • Ch 169: Min-Cost Max-Flow — the base chapter on this topic