DSA DSA In Depth
0%
design flow-reductionsmin-cut-modelingcirculation

Network Flow Modeling Patterns: Closure Problem (Project Selection), Circulation with Demands & Image Segmentation

7 min read Module 5.3: Network Flow, Min-Cut & Bipartite Matching Updated Oct 23, 2026
📄 Cheat Sheet
Maximum Flow & Minimum Cut Reductions: The Project Selection (Closure) Problem
Thomas Meeks Maximum Flow & Minimum Cut Reductions: The Project Selection (Closure) Problem Watch on YouTube ↗

Shows how non-obvious optimization problems reduce to s-t Min-Cut and lower-bound circulation.

0:00 / --:--

Pattern 1: Maximum-Weight Closure (Project Selection) via $s$-$t$ Min-Cut

Suppose you have $N$ projects where project $i$ yields net value $w_i \in \mathbb{R}$ ($w_i > 0$ for profitable projects, $w_i < 0$ for prerequisite infrastructure/machines), and a dependency DAG where choosing $u$ requires also choosing $v$ ($u \to v$). A valid selection $A \subseteq V$ is a closure (no edge leaves $A$).

Reduction Theorem — Maximum-Weight Closure to Minimum $s$-$t$ Cut (Picard 1976)

Construct a flow network on $V \cup \{s, t\}$:

  • For each profitable node $u$ with $w_u > 0$, add edge $(s, u)$ with capacity $w_u$.
  • For each costly node $v$ with $w_v < 0$, add edge $(v, t)$ with capacity $|w_v| = -w_v$.
  • For each prerequisite dependency $u \to v$, add edge $(u, v)$ with capacity $\infty$.

For any finite-capacity cut $(S, T)$, no $\infty$ edge can cross from $S$ to $T$, so $A = S \setminus \{s\}$ is a valid closure! Its cut capacity is:

$$c(S, T) = \sum_{u \in T, w_u > 0} w_u + \sum_{v \in S, w_v < 0} |w_v| = \underbrace{\sum_{w_u > 0} w_u}_{W^+} - \sum_{u \in A} w_u$$

Therefore, minimizing $c(S, T)$ maximizes $\sum_{u \in A} w_u = W^+ - \text{MinCut}(s, t)$! $\blacksquare$

flowchart LR S["Source s"] -->|"cap = w_u (Profit)"| P1["Profitable Project u (w_u > 0)"] P1 -->|"cap = INF (Prerequisite)"| M1["Costly Machine v (w_v < 0)"] M1 -->|"cap = |w_v| (Cost)"| T["Sink t"]
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.

Pattern 2: Flow with Edge Lower Bounds $L(e) \le f(e) \le U(e)$

What if every edge $e = (u, v)$ requires at least $L(e)$ units of flow: $L(e) \le f(e) \le U(e)$? Write $f(e) = L(e) + f'(e)$ where $0 \le f'(e) \le U(e) - L(e)$. Pre-committing $L(e)$ creates a vertex net balance $b(v) = \sum_{e \in \text{in}(v)} L(e) - \sum_{e \in \text{out}(v)} L(e)$ that $f'$ must absorb:

  • Add a super-source $S^*$ and super-sink $T^*$.
  • For each vertex $v$ with $b(v) > 0$ (excess incoming lower-bound flow), add edge $(S^*, v)$ of capacity $b(v)$.
  • For each vertex $v$ with $b(v) < 0$ (deficit), add edge $(v, T^*)$ of capacity $-b(v)$.
  • For an $s$-$t$ flow network (not a closed circulation), add a feedback edge $(t, s)$ with capacity $\infty$.

A feasible circulation exists if and only if $\text{MaxFlow}(S^*, T^*) = \sum_{b(v) > 0} b(v)$ (saturating all outgoing edges from $S^*$)!

Pattern 3 — Vertex Capacities via Node Splitting ($u_{\text{in}} \to u_{\text{out}}$)

Standard flow networks only constrain edge capacities. Whenever a vertex $u$ can pass at most $C_u$ units of flow (or in vertex-disjoint paths / minimum vertex cut), split $u$ into two nodes $u_{\text{in}} = 2u$ and $u_{\text{out}} = 2u + 1$ connected by an internal directed edge $(u_{\text{in}}, u_{\text{out}})$ of capacity $C_u$, routing all incoming edges into $u_{\text{in}}$ and all outgoing edges from $u_{\text{out}}$.

Modeling ProblemGraph ConstructionObjective Formula
Project Selection (Max Closure)$s \xrightarrow{w_u} u \xrightarrow{\infty} v \xrightarrow{|w_v|} t$$\sum_{w_u > 0} w_u - \text{MaxFlow}(s, t)$
Vertex Capacity $C_u$Split $u \mapsto u_{\text{in}} \xrightarrow{C_u} u_{\text{out}}$Standard Max-Flow / Min-Cut on $2V$ nodes
Lower-Bound CirculationCap $U(e)-L(e)$; connect $S^* \to v$ ($b_v > 0$), $v \to T^*$ ($b_v < 0$)Feasible iff $\text{MaxFlow}(S^*, T^*) == \sum_{b_v > 0} b_v$
Bipartite Min Weight Vertex Cover$s \xrightarrow{w_u} u \in L$, $u \xrightarrow{\infty} v$, $v \in R \xrightarrow{w_v} t$$\text{MinCut}(s, t) = \text{MaxFlow}(s, t)$

Production C++20 Implementation: Maximum-Weight Closure (Project Selection) Solver

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

struct ClosureResult {
    long long maxNetProfit;
    std::vector<int> selectedProjects;
};

// weights[i] > 0: profit of project i; weights[i] < 0: cost of prerequisite i
// dependencies: pairs (u, v) meaning selecting u requires selecting v
ClosureResult solveMaxWeightClosure(
    const std::vector<long long>& weights,
    const std::vector<std::pair<int, int>>& dependencies) {

    int n = static_cast<int>(weights.size());
    int s = n, t = n + 1;
    constexpr long long INF = std::numeric_limits<long long>::max() / 4;

    Dinic dinic(n + 2);
    long long totalPositiveProfit = 0;

    for (int i = 0; i < n; ++i) {
        if (weights[i] > 0) {
            totalPositiveProfit += weights[i];
            dinic.addEdge(s, i, weights[i]);
        } else if (weights[i] < 0) {
            dinic.addEdge(i, t, -weights[i]);
        }
    }
    for (auto [u, v] : dependencies) {
        dinic.addEdge(u, v, INF);
    }

    long long minCutLoss = dinic.maxFlow(s, t);
    // Selected projects are vertices in S (reachable from s in final residual graph)
    // Note: in our Dinic class, final BFS sets level[u] != -1 iff u is reachable from s in G_f!
    return {totalPositiveProfit - minCutLoss, {}};
}
Failure Mode — Using LLONG_MAX for $\infty$ Edges (Integer Overflow)

When modeling hard constraints with $\infty$ edges, never use std::numeric_limits<long long>::max() directly if your flow solver or cut summation ever adds two capacities together. Set INF to strictly exceed $\sum_{w_u > 0} w_u + 1$ (e.g., 1e15), which guarantees no min-cut can ever sever a hard dependency edge while preventing 64-bit integer overflow.

Check yourself Question 1 of 4
Concept Check Direction of Infinite-Capacity Dependency Edges in Max-Weight Closure recall
Section: Project Selection Reduction

In the Maximum-Weight Closure (Project Selection) reduction to $s$-$t$ Min-Cut, where $u \in S$ means project $u$ is selected and $u \in T$ means project $u$ is not selected: if selecting project $u$ **requires** also selecting prerequisite $v$, how must the $\infty$-capacity edge be directed?

⚡ Interactive Challenge · Staff Level

Algorithmic Design & Complexity Check

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

To test if a network admits a feasible circulation satisfying $L(e) \le f(e) \le U(e)$, we set edge capacities to $U(e) - L(e)$, compute vertex excess $b(v) = \sum_{\text{in}} L(e) - \sum_{\text{out}} L(e)$, connect $S^* \to v$ (cap $b(v)$ if $b(v) > 0$) and $v \to T^*$ (cap $-b(v)$ if $b(v) < 0$). When does a feasible circulation exist?

Key Takeaways

An s-t minimum cut acts as a binary classifier (u in S vs u in T) where edge (u, v) charges penalty c(u, v) iff u in S and v in T.
Maximum-Weight Closure (Project Selection) reduces to s-t Min-Cut: MaxProfit = sum_{w_u > 0} w_u - MinCut(s, t), with selected projects equal to S \ {s}.
Edge lower bounds L(e) <= f(e) <= U(e) reduce to standard max-flow by setting residual capacity U(e) - L(e) and routing vertex imbalances b(v) from S* and to T*.
Vertex capacities are modeled by splitting each node u into u_in -> u_out with an internal edge of capacity C_u.
Was this lesson helpful? Spot an issue or have a suggestion? Let us know on GitHub.
Report an issue