Max-flow/min-cut theorem
Every flow is at most the capacity of every cut, and when no augmenting path exists some cut has exactly the flow's value: maximum flow equals minimum cut, and the residual graph hands you both.
Learning goals
- Compute the capacity of a cut and the flow across it
- Explain why every flow value is at most every cut capacity
- Use residual reachability as an optimality certificate
- Extract a minimum cut from a maximum flow
Prerequisites
To find the narrowest bottleneck in a road network, draw a line separating the source from the sink and add up the capacities of the roads that cross it. The cheapest such line is a bottleneck no traffic can beat, and the theorem says it is exactly as wide as the best traffic pattern.
Cut capacity
An **s-t cut** is a set S of vertices that contains the source and not the sink. Its **capacity** is the sum of the capacities of the edges that go **from** S **to** the rest. Edges coming back into S do not count.
Load the cut question with “Cut capacity of S = {0, 1}” on the sample flow: the edges leaving {0, 1} are 0-2 (2), 1-2 (1) and 1-3 (2), so the capacity is 5. With “Cut capacity of S = {0}” the leaving edges are 0-1 (3) and 0-2 (2), also 5.
The key fact: for **any** valid flow, the flow value equals (flow out of S) minus (flow into S). Everything that leaves the source must cross the boundary of S somewhere, and conservation stops it piling up inside. The animation prints both numbers: on the sample flow 4 units leave and 0 return across both cuts.
1static int cutCapacity(int[][] edges, boolean[] inS) { // capacity of the edges leaving S (edges {from, to, capacity, flow})2int cap = 0;3for (int[] e : edges) if (inS[e[0]] && !inS[e[1]]) cap += e[2];4return cap;5}67static int flowAcrossCut(int[][] edges, boolean[] inS) { // flow leaving S minus flow entering S8int net = 0;9for (int[] e : edges) {10if (inS[e[0]] && !inS[e[1]]) net += e[3];11if (!inS[e[0]] && inS[e[1]]) net -= e[3];12}13return net;14}
A flow on a network: validity, residual graph, cuts and the optimality certificate
Interactive · edit the inputedge label: flow / capacity · 4 vertices · 5 edges
source
sink
A flow assigns each edge an amount between 0 and its capacity such that every vertex except the source 0 and the sink 3 receives as much as it sends. Label: flow / capacity.
1// edges[k] = {from, to, capacity, flow}; the source is 0 and the sink is n - 12static boolean isValidFlow(int n, int[][] edges) {3int[] balance = new int[n]; // inflow minus outflow4for (int[] e : edges) {5if (e[3] < 0 || e[3] > e[2]) return false; // capacity constraint: 0 <= flow <= capacity6balance[e[0]] -= e[3];7balance[e[1]] += e[3];8}9for (int v = 1; v < n - 1; v++) if (balance[v] != 0) return false; // conservation at every inner vertex10return true;11}12static int flowValue(int n, int[][] edges) { // net flow leaving the source13int value = 0;14for (int[] e : edges) { if (e[0] == 0) value += e[3]; if (e[1] == 0) value -= e[3]; }15return value;16}17static List<int[]> residualArcs(int[][] edges) { // {from, to, remaining capacity}18List<int[]> arcs = new ArrayList<>();19for (int[] e : edges) {20if (e[2] - e[3] > 0) arcs.add(new int[]{e[0], e[1], e[2] - e[3]}); // forward: room left21if (e[3] > 0) arcs.add(new int[]{e[1], e[0], e[3]}); // backward: flow that could be cancelled22}23return arcs;24}25static int cutCapacity(int[][] edges, boolean[] inS) { // capacity of the edges leaving S26int cap = 0;27for (int[] e : edges) if (inS[e[0]] && !inS[e[1]]) cap += e[2];28return cap;29}30static boolean[] reachableInResidual(int n, int[][] edges) { // vertices reachable from the source using residual arcs31boolean[] seen = new boolean[n];32seen[0] = true;33boolean changed = true;34while (changed) {35changed = false;36for (int[] a : residualArcs(edges)) if (seen[a[0]] && !seen[a[1]]) { seen[a[1]] = true; changed = true; }37}38return seen;39}40static boolean isMaximum(int n, int[][] edges) { // valid flow, and the sink is cut off in the residual graph41return isValidFlow(n, edges) && !reachableInResidual(n, edges)[n - 1];42}
Live state
n
4
edges
5
Execution trace (dry run table)Show 10 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | n=4 edges=5 | A flow assigns each edge an amount between 0 and its capacity such that every vertex except the source 0 and the sink 3 receives as much as it sends. Label: flow / capacity. |
| 2 | L5 | edge=0→1 flow=2 capacity=3 | Edge 0→1: 0 ≤ 2 ≤ 3. Capacity respected. |
| 3 | L5 | edge=0→2 flow=2 capacity=2 | Edge 0→2: 0 ≤ 2 ≤ 2. Capacity respected. |
| 4 | L5 | edge=1→2 flow=0 capacity=1 | Edge 1→2: 0 ≤ 0 ≤ 1. Capacity respected. |
| 5 | L5 | edge=1→3 flow=2 capacity=2 | Edge 1→3: 0 ≤ 2 ≤ 2. Capacity respected. |
| 6 | L5 | edge=2→3 flow=2 capacity=3 | Edge 2→3: 0 ≤ 2 ≤ 3. Capacity respected. |
| 7 | L9 | vertex=1 inflow=2 outflow=2 | Vertex 1: inflow 2 = outflow 2. Conservation holds. |
| 8 | L9 | vertex=2 inflow=2 outflow=2 | Vertex 2: inflow 2 = outflow 2. Conservation holds. |
| 9 | L12 | value=4 | Valid. The flow value is the net amount leaving the source: 4. It equals the net amount entering the sink. |
| 10 | L17 | return=4 residual_arcs=7 | Residual graph: each edge with room left gives a forward arc (capacity − flow); each edge with flow gives a reverse arc (the flow that could be cancelled). 7 arcs in all; the arcs marked * are reverse arcs. |
Residual reachability
After a flow has been computed, run a search from the source in the residual graph and let S be every vertex it reaches. Two outcomes are possible. Either the sink is in S: you found an augmenting path. Or the sink is not in S: no augmenting path exists.
In the second case look at the boundary of S. An original edge leaving S must be **saturated** (flow = capacity), otherwise its forward arc would have let the search cross. An original edge entering S must carry **zero** flow, otherwise its backward arc would let the search cross. So across this boundary all edges are as full as possible going out and empty coming in.
Load the third question with “Maximum: the residual graph cuts off the sink”: the search reaches only {0}, and the two edges leaving 0 are both saturated.
1static boolean[] reachableInResidual(int n, int[][] edges) { // vertices reachable from the source along residual arcs2boolean[] seen = new boolean[n];3seen[0] = true;4boolean changed = true;5while (changed) {6changed = false;7for (int[] a : residualArcs(edges)) if (seen[a[0]] && !seen[a[1]]) { seen[a[1]] = true; changed = true; }8}9return seen;10}
A flow on a network: validity, residual graph, cuts and the optimality certificate
Interactive · edit the inputedge label: flow / capacity · 4 vertices · 5 edges
source
sink
A flow assigns each edge an amount between 0 and its capacity such that every vertex except the source 0 and the sink 3 receives as much as it sends. Label: flow / capacity.
1// edges[k] = {from, to, capacity, flow}; the source is 0 and the sink is n - 12static boolean isValidFlow(int n, int[][] edges) {3int[] balance = new int[n]; // inflow minus outflow4for (int[] e : edges) {5if (e[3] < 0 || e[3] > e[2]) return false; // capacity constraint: 0 <= flow <= capacity6balance[e[0]] -= e[3];7balance[e[1]] += e[3];8}9for (int v = 1; v < n - 1; v++) if (balance[v] != 0) return false; // conservation at every inner vertex10return true;11}12static int flowValue(int n, int[][] edges) { // net flow leaving the source13int value = 0;14for (int[] e : edges) { if (e[0] == 0) value += e[3]; if (e[1] == 0) value -= e[3]; }15return value;16}17static List<int[]> residualArcs(int[][] edges) { // {from, to, remaining capacity}18List<int[]> arcs = new ArrayList<>();19for (int[] e : edges) {20if (e[2] - e[3] > 0) arcs.add(new int[]{e[0], e[1], e[2] - e[3]}); // forward: room left21if (e[3] > 0) arcs.add(new int[]{e[1], e[0], e[3]}); // backward: flow that could be cancelled22}23return arcs;24}25static int cutCapacity(int[][] edges, boolean[] inS) { // capacity of the edges leaving S26int cap = 0;27for (int[] e : edges) if (inS[e[0]] && !inS[e[1]]) cap += e[2];28return cap;29}30static boolean[] reachableInResidual(int n, int[][] edges) { // vertices reachable from the source using residual arcs31boolean[] seen = new boolean[n];32seen[0] = true;33boolean changed = true;34while (changed) {35changed = false;36for (int[] a : residualArcs(edges)) if (seen[a[0]] && !seen[a[1]]) { seen[a[1]] = true; changed = true; }37}38return seen;39}40static boolean isMaximum(int n, int[][] edges) { // valid flow, and the sink is cut off in the residual graph41return isValidFlow(n, edges) && !reachableInResidual(n, edges)[n - 1];42}
Live state
n
4
edges
5
Execution trace (dry run table)Show 10 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | n=4 edges=5 | A flow assigns each edge an amount between 0 and its capacity such that every vertex except the source 0 and the sink 3 receives as much as it sends. Label: flow / capacity. |
| 2 | L5 | edge=0→1 flow=2 capacity=3 | Edge 0→1: 0 ≤ 2 ≤ 3. Capacity respected. |
| 3 | L5 | edge=0→2 flow=2 capacity=2 | Edge 0→2: 0 ≤ 2 ≤ 2. Capacity respected. |
| 4 | L5 | edge=1→2 flow=0 capacity=1 | Edge 1→2: 0 ≤ 0 ≤ 1. Capacity respected. |
| 5 | L5 | edge=1→3 flow=2 capacity=2 | Edge 1→3: 0 ≤ 2 ≤ 2. Capacity respected. |
| 6 | L5 | edge=2→3 flow=2 capacity=3 | Edge 2→3: 0 ≤ 2 ≤ 3. Capacity respected. |
| 7 | L9 | vertex=1 inflow=2 outflow=2 | Vertex 1: inflow 2 = outflow 2. Conservation holds. |
| 8 | L9 | vertex=2 inflow=2 outflow=2 | Vertex 2: inflow 2 = outflow 2. Conservation holds. |
| 9 | L12 | value=4 | Valid. The flow value is the net amount leaving the source: 4. It equals the net amount entering the sink. |
| 10 | L17 | return=4 residual_arcs=7 | Residual graph: each edge with room left gives a forward arc (capacity − flow); each edge with flow gives a reverse arc (the flow that could be cancelled). 7 arcs in all; the arcs marked * are reverse arcs. |
Optimality certificate
Because flow across the cut equals the flow value, and the flow out of S cannot exceed the capacity of the edges leaving S, **every flow value is at most every cut capacity**. Therefore if you exhibit one flow and one cut with equal numbers, the flow is maximum and the cut is minimum: the pair is a **certificate** anyone can check without trusting your algorithm.
The residual search builds that certificate automatically. When the sink is unreachable, the reachable set S is a cut whose capacity equals the flow value (all its leaving edges are saturated and all its entering edges are empty). On “Maximum: the residual graph cuts off the sink” the flow value is 5 and the cut {0} has capacity 5.
Checking is cheap: verify the two flow rules, run one residual search, and confirm the sink is unreachable.
1static boolean isMaximum(int n, int[][] edges) { // valid flow whose residual graph cuts the sink off2return respectsCapacity(edges) && conservationHolds(n, edges) && !reachableInResidual(n, edges)[n - 1];3}
A flow on a network: validity, residual graph, cuts and the optimality certificate
Interactive · edit the inputedge label: flow / capacity · 4 vertices · 5 edges
source
sink
A flow assigns each edge an amount between 0 and its capacity such that every vertex except the source 0 and the sink 3 receives as much as it sends. Label: flow / capacity.
1// edges[k] = {from, to, capacity, flow}; the source is 0 and the sink is n - 12static boolean isValidFlow(int n, int[][] edges) {3int[] balance = new int[n]; // inflow minus outflow4for (int[] e : edges) {5if (e[3] < 0 || e[3] > e[2]) return false; // capacity constraint: 0 <= flow <= capacity6balance[e[0]] -= e[3];7balance[e[1]] += e[3];8}9for (int v = 1; v < n - 1; v++) if (balance[v] != 0) return false; // conservation at every inner vertex10return true;11}12static int flowValue(int n, int[][] edges) { // net flow leaving the source13int value = 0;14for (int[] e : edges) { if (e[0] == 0) value += e[3]; if (e[1] == 0) value -= e[3]; }15return value;16}17static List<int[]> residualArcs(int[][] edges) { // {from, to, remaining capacity}18List<int[]> arcs = new ArrayList<>();19for (int[] e : edges) {20if (e[2] - e[3] > 0) arcs.add(new int[]{e[0], e[1], e[2] - e[3]}); // forward: room left21if (e[3] > 0) arcs.add(new int[]{e[1], e[0], e[3]}); // backward: flow that could be cancelled22}23return arcs;24}25static int cutCapacity(int[][] edges, boolean[] inS) { // capacity of the edges leaving S26int cap = 0;27for (int[] e : edges) if (inS[e[0]] && !inS[e[1]]) cap += e[2];28return cap;29}30static boolean[] reachableInResidual(int n, int[][] edges) { // vertices reachable from the source using residual arcs31boolean[] seen = new boolean[n];32seen[0] = true;33boolean changed = true;34while (changed) {35changed = false;36for (int[] a : residualArcs(edges)) if (seen[a[0]] && !seen[a[1]]) { seen[a[1]] = true; changed = true; }37}38return seen;39}40static boolean isMaximum(int n, int[][] edges) { // valid flow, and the sink is cut off in the residual graph41return isValidFlow(n, edges) && !reachableInResidual(n, edges)[n - 1];42}
Live state
n
4
edges
5
Execution trace (dry run table)Show 10 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | n=4 edges=5 | A flow assigns each edge an amount between 0 and its capacity such that every vertex except the source 0 and the sink 3 receives as much as it sends. Label: flow / capacity. |
| 2 | L5 | edge=0→1 flow=2 capacity=3 | Edge 0→1: 0 ≤ 2 ≤ 3. Capacity respected. |
| 3 | L5 | edge=0→2 flow=2 capacity=2 | Edge 0→2: 0 ≤ 2 ≤ 2. Capacity respected. |
| 4 | L5 | edge=1→2 flow=0 capacity=1 | Edge 1→2: 0 ≤ 0 ≤ 1. Capacity respected. |
| 5 | L5 | edge=1→3 flow=2 capacity=2 | Edge 1→3: 0 ≤ 2 ≤ 2. Capacity respected. |
| 6 | L5 | edge=2→3 flow=2 capacity=3 | Edge 2→3: 0 ≤ 2 ≤ 3. Capacity respected. |
| 7 | L9 | vertex=1 inflow=2 outflow=2 | Vertex 1: inflow 2 = outflow 2. Conservation holds. |
| 8 | L9 | vertex=2 inflow=2 outflow=2 | Vertex 2: inflow 2 = outflow 2. Conservation holds. |
| 9 | L12 | value=4 | Valid. The flow value is the net amount leaving the source: 4. It equals the net amount entering the sink. |
| 10 | L17 | return=4 residual_arcs=7 | Residual graph: each edge with room left gives a forward arc (capacity − flow); each edge with flow gives a reverse arc (the flow that could be cancelled). 7 arcs in all; the arcs marked * are reverse arcs. |
Flow-cut equality
**Max-flow min-cut theorem**: the value of a maximum flow equals the capacity of a minimum cut. The easy half (flow <= cut) is the argument above; the hard half is that equality is always reached, and it comes from the residual search: when an algorithm stops because no augmenting path remains, the reachable set is a cut of exactly the flow's value.
Try it: run the “Ford-Fulkerson on a small network” preset. The maximum flow is 19 and the final message names the minimum cut, the vertices {0, 2}; the edges leaving that set are 0→1 (capacity 10) and 2→4 (capacity 9), together 19. Every algorithm in the next lessons ends with this same message, because every one of them stops exactly when the residual graph has no augmenting path (push-relabel stops at an equivalent point).
This duality is the reason flow algorithms solve so many problems: finding the cheapest way to disconnect two vertices is the same problem as finding the largest amount that can be routed between them.
1static List<int[]> minCutEdges(int n, int[][] edges) { // for a maximum flow: the saturated edges leaving the reachable set2boolean[] inS = reachableInResidual(n, edges);3List<int[]> cut = new ArrayList<>();4for (int[] e : edges) if (inS[e[0]] && !inS[e[1]]) cut.add(e);5return cut;6}
Maximum flow: Ford-Fulkerson, Edmonds-Karp, Dinic, push-relabel
Interactive · edit the inputedge label: flow / capacity · 6 vertices · 9 edges
source
sink
residual arcs u→v : remaining capacity
Every edge starts with flow 0, so every arc has its full capacity as residual capacity and every reverse arc has 0. Edmonds-Karp (BFS paths) looks for ways to send more from 0 to 5. The label on an edge is flow / capacity.
1static class Net {2final int n;3final List<Integer>[] adj; // adj[u]: ids of the arcs leaving u4final int[] to, cap; // arc k and arc k ^ 1 are each other's reverse5int arcs = 0;6@SuppressWarnings("unchecked")7Net(int n, int maxEdges) {8this.n = n; adj = new List[n]; to = new int[2 * maxEdges]; cap = new int[2 * maxEdges];9for (int i = 0; i < n; i++) adj[i] = new ArrayList<>();10}11void addEdge(int u, int v, int c) {12to[arcs] = v; cap[arcs] = c; adj[u].add(arcs++); // forward arc with the capacity13to[arcs] = u; cap[arcs] = 0; adj[v].add(arcs++); // reverse arc: capacity 0 until flow is pushed14}15}16static int fordFulkerson(Net g, int s, int t) {17int flow = 0;18while (true) {19int pushed = dfs(g, s, t, Integer.MAX_VALUE, new boolean[g.n]); // any augmenting path, found depth-first20if (pushed == 0) return flow;21flow += pushed;22}23}24static int dfs(Net g, int u, int t, int f, boolean[] seen) {25if (u == t) return f;26seen[u] = true;27for (int id : g.adj[u]) {28int v = g.to[id];29if (seen[v] || g.cap[id] == 0) continue;30int d = dfs(g, v, t, Math.min(f, g.cap[id]), seen);31if (d > 0) { g.cap[id] -= d; g.cap[id ^ 1] += d; return d; } // use d units, open d units of reverse capacity32}33return 0;34}35static int edmondsKarp(Net g, int s, int t) {36int flow = 0;37while (true) {38int[] viaArc = new int[g.n];39Arrays.fill(viaArc, -1);40Deque<Integer> queue = new ArrayDeque<>(List.of(s));41while (!queue.isEmpty() && viaArc[t] == -1) { // BFS: the path with fewest arcs42int u = queue.poll();43for (int id : g.adj[u]) {44int v = g.to[id];45if (v != s && viaArc[v] == -1 && g.cap[id] > 0) { viaArc[v] = id; queue.add(v); }46}47}48if (viaArc[t] == -1) return flow;49int bottleneck = Integer.MAX_VALUE;50for (int v = t; v != s; v = g.to[viaArc[v] ^ 1]) bottleneck = Math.min(bottleneck, g.cap[viaArc[v]]);51for (int v = t; v != s; v = g.to[viaArc[v] ^ 1]) { g.cap[viaArc[v]] -= bottleneck; g.cap[viaArc[v] ^ 1] += bottleneck; }52flow += bottleneck;53}54}55static int dinic(Net g, int s, int t) {56int flow = 0;57int[] level = new int[g.n];58while (levels(g, s, t, level)) { // new level graph; stop when t is unreachable59int[] next = new int[g.n]; // next arc to try at each vertex60for (int f; (f = blocking(g, s, t, Integer.MAX_VALUE, level, next)) > 0; ) flow += f;61}62return flow;63}64static boolean levels(Net g, int s, int t, int[] level) {65Arrays.fill(level, -1);66level[s] = 0;67Deque<Integer> queue = new ArrayDeque<>(List.of(s));68while (!queue.isEmpty()) {69int u = queue.poll();70for (int id : g.adj[u]) if (g.cap[id] > 0 && level[g.to[id]] == -1) { level[g.to[id]] = level[u] + 1; queue.add(g.to[id]); }71}72return level[t] != -1;73}74static int blocking(Net g, int u, int t, int f, int[] level, int[] next) {75if (u == t) return f;76for (; next[u] < g.adj[u].size(); next[u]++) {77int id = g.adj[u].get(next[u]), v = g.to[id];78if (g.cap[id] == 0 || level[v] != level[u] + 1) continue; // only arcs one level deeper79int d = blocking(g, v, t, Math.min(f, g.cap[id]), level, next);80if (d > 0) { g.cap[id] -= d; g.cap[id ^ 1] += d; return d; }81}82return 0;83}84static int pushRelabel(Net g, int s, int t) {85int[] height = new int[g.n], excess = new int[g.n];86height[s] = g.n; // the source starts as high as possible87Deque<Integer> active = new ArrayDeque<>();88for (int id : g.adj[s]) { // saturate every arc out of the source89int c = g.cap[id], v = g.to[id];90if (c == 0) continue;91g.cap[id] = 0; g.cap[id ^ 1] += c; excess[v] += c;92if (v != t && excess[v] == c) active.add(v);93}94while (!active.isEmpty()) discharge(g, active.poll(), s, t, height, excess, active);95return excess[t];96}97static void discharge(Net g, int u, int s, int t, int[] height, int[] excess, Deque<Integer> active) {98while (excess[u] > 0) {99boolean pushed = false;100for (int id : g.adj[u]) {101int v = g.to[id];102if (g.cap[id] > 0 && height[u] == height[v] + 1) { // push only downhill, by exactly one level103int d = Math.min(excess[u], g.cap[id]);104g.cap[id] -= d; g.cap[id ^ 1] += d; excess[u] -= d; excess[v] += d;105if (v != s && v != t && excess[v] == d) active.add(v);106pushed = true;107if (excess[u] == 0) break;108}109}110if (!pushed) { // stuck: relabel one above its lowest residual neighbour111int lowest = Integer.MAX_VALUE;112for (int id : g.adj[u]) if (g.cap[id] > 0) lowest = Math.min(lowest, height[g.to[id]]);113height[u] = lowest + 1;114}115}116}
Live state
n
6
edges
9
source
0
sink
5
Execution trace (dry run table)Show 11 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L16 | n=6 edges=9 source=0 sink=5 | Every edge starts with flow 0, so every arc has its full capacity as residual capacity and every reverse arc has 0. Edmonds-Karp (BFS paths) looks for ways to send more from 0 to 5. The label on an edge is flow / capacity. |
| 2 | L48 | path=0→1→3→5 bottleneck=4 flow=0 | Breadth-first search (fewest arcs) finds the path 0→1→3→5 with 3 arcs. Its smallest residual capacity is 4. |
| 3 | L51 | bottleneck=4 flow=4 | Push 4 along the path. Flow value is now 4. |
| 4 | L48 | path=0→1→4→5 bottleneck=6 flow=4 | Breadth-first search (fewest arcs) finds the path 0→1→4→5 with 3 arcs. Its smallest residual capacity is 6. |
| 5 | L51 | bottleneck=6 flow=10 | Push 6 along the path. Flow value is now 10. |
| 6 | L48 | path=0→2→4→5 bottleneck=4 flow=10 | Breadth-first search (fewest arcs) finds the path 0→2→4→5 with 3 arcs. Its smallest residual capacity is 4. |
| 7 | L51 | bottleneck=4 flow=14 | Push 4 along the path. Flow value is now 14. |
| 8 | L48 | path=0→2→4→3→5 bottleneck=5 flow=14 | Breadth-first search (fewest arcs) finds the path 0→2→4→3→5 with 4 arcs. Its smallest residual capacity is 5. |
| 9 | L51 | bottleneck=5 flow=19 | Push 5 along the path. Flow value is now 19. |
| 10 | L48 | flow=19 | No path with positive residual capacity from 0 to 5 remains: the search ends. |
| 11 | L16 | return=19 cut_capacity=19 augmentations=4 | Maximum flow = 19. The vertices still reachable from 0 in the residual graph, {0, 2}, form the minimum cut: the edges leaving that set (0→1, 2→4) are all saturated and their capacities add up to 19, the same number. |
Practice ladder
Now try it yourself
Easy
- Find if Path Exists in Graph
Medium
- Minimum Number of Days to Disconnect Island
- Number of Operations to Make Network Connected
Advanced
- Minimum Cost to Make at Least One Valid Path in a Grid
- Reachable Nodes In Subdivided Graph
Key takeaways
- Cut capacity counts only edges leaving S.
- Every flow value <= every cut capacity; equal values certify both are optimal.
- When no augmenting path remains, the vertices reachable from the source form a minimum cut.