Download Speed
Problem Overview
| Aspect | Details |
|---|---|
| Problem | Find maximum data transfer rate from source to sink |
| Type | Maximum Flow |
| Difficulty | Hard |
| Key Algorithm | Edmonds-Karp (Ford-Fulkerson with BFS) |
| Time Complexity | O(VE^2) |
| Space Complexity | O(V^2) |
Learning Goals
By completing this problem, you will understand:
- Maximum Flow Concept: How to model network bandwidth as a flow problem
- Residual Graphs: Tracking remaining capacity after using some flow
- Augmenting Paths: Finding paths that can carry additional flow
- Ford-Fulkerson Method: The general framework for max flow algorithms
- Edmonds-Karp Algorithm: Using BFS to guarantee polynomial time complexity
Problem Statement
You have a network of n computers numbered 1 to n. Computer 1 is the server and computer n is your computer. There are m connections between computers, each with a maximum bandwidth (data transfer capacity).
Goal: Find the maximum rate at which data can be transferred from computer 1 to computer n.
Input:
- First line: n (computers) and m (connections)
- Next m lines: a, b, c meaning connection from a to b with bandwidth c
Output: Maximum data transfer rate from 1 to n
Constraints:
- 1 <= n <= 500
- 1 <= m <= 1000
- 1 <= a, b <= n
- 1 <= c <= 10^9
Core Concepts
What is Maximum Flow?
Maximum flow finds the greatest amount of “flow” that can be sent from a source to a sink through a network where each edge has a capacity limit.
Key properties:
- Capacity Constraint: Flow on edge (u,v) cannot exceed its capacity
- Flow Conservation: For non-source/sink nodes, inflow = outflow
- Skew Symmetry: flow(u,v) = -flow(v,u)
Residual Graph
The residual graph shows remaining capacity after some flow is used.
For each edge (u,v) with capacity c and current flow f:
- Forward edge: remaining capacity = c - f
- Backward edge: remaining capacity = f (allows “undoing” flow)
Original Graph: After sending 3 units: Residual Graph:
[A]---5--->[B] [A]--3/5-->[B] [A]---2--->[B]
<---3---
Capacity: 5 Flow: 3 Forward: 5-3=2
Backward: 3 (reverse)
Why Backward Edges?
Backward edges allow the algorithm to “undo” previous decisions. Without them, a greedy choice might block the optimal solution.
Example where backward edges are essential:
2
A-------->B
| |
3 | | 3
v v
S T
| |
3 | | 2
v v
C-------->D
3
Without backward edges: Path S->A->B->T uses 2, Path S->C->D->T uses 2
Total: 4
With backward edges: Can find path S->A->B->D->T (uses B->D backward)
Then S->C->D->T uses forward edge
Total: 5 (optimal)
Ford-Fulkerson Algorithm
The Ford-Fulkerson method repeatedly finds augmenting paths until none exist.
Algorithm Steps
1. Initialize: flow = 0 for all edges
2. Build residual graph from original capacities
3. While augmenting path exists from source to sink:
a. Find path P in residual graph
b. Find bottleneck = min capacity along P
c. Augment flow by bottleneck:
- Subtract bottleneck from forward edges
- Add bottleneck to backward edges
d. Add bottleneck to total flow
4. Return total flow
Edmonds-Karp: BFS for Path Finding
Using BFS (instead of DFS) guarantees O(VE^2) complexity because:
- BFS finds shortest augmenting path (by edge count)
- Each edge becomes critical (bottleneck) at most O(V) times
- At most O(E) edges, so O(VE) augmentations
- Each BFS takes O(E), total: O(VE^2)
Visual Walkthrough
Example Network
Input:
4 5
1 2 6
1 3 4
2 3 2
2 4 3
3 4 5
Network Structure:
6
[1]------>[2]
| \ |
4 | \ 2 | 3
v v v
[3]------>[4]
5
Dry Run
Initial State:
Residual capacities:
1->2: 6 2->3: 2 3->4: 5
1->3: 4 2->4: 3
Total flow: 0
Iteration 1: BFS finds path 1 -> 2 -> 4
Path: 1 -> 2 -> 4
Bottleneck: min(6, 3) = 3
Update residual graph:
1->2: 6-3=3 2->1: 0+3=3
2->4: 3-3=0 4->2: 0+3=3
Total flow: 0 + 3 = 3
Iteration 2: BFS finds path 1 -> 3 -> 4
Path: 1 -> 3 -> 4
Bottleneck: min(4, 5) = 4
Update residual graph:
1->3: 4-4=0 3->1: 0+4=4
3->4: 5-4=1 4->3: 0+4=4
Total flow: 3 + 4 = 7
Iteration 3: BFS finds path 1 -> 2 -> 3 -> 4
Path: 1 -> 2 -> 3 -> 4
Bottleneck: min(3, 2, 1) = 1
Update residual graph:
1->2: 3-1=2 2->1: 3+1=4
2->3: 2-1=1 3->2: 0+1=1
3->4: 1-1=0 4->3: 4+1=5
Total flow: 7 + 1 = 8
Iteration 4: No augmenting path exists
BFS from 1 cannot reach 4 (all paths blocked)
Algorithm terminates
Maximum Flow: 8
Final Flow Distribution:
Edge 1->2: 4 units (capacity 6)
Edge 1->3: 4 units (capacity 4)
Edge 2->3: 1 unit (capacity 2)
Edge 2->4: 3 units (capacity 3)
Edge 3->4: 5 units (capacity 5)
Verification:
- Outflow from 1: 4 + 4 = 8
- Inflow to 4: 3 + 5 = 8
Implementation
Python Solution
from collections import deque
def max_flow(n, edges):
"""
Find maximum flow from node 1 to node n.
Uses Edmonds-Karp algorithm (Ford-Fulkerson with BFS).
"""
# Build adjacency list and capacity matrix
# Using dict for capacity to handle parallel edges
adj = [[] for _ in range(n + 1)]
capacity = {}
for u, v, c in edges:
# Add forward edge
if (u, v) not in capacity:
adj[u].append(v)
adj[v].append(u) # Reverse edge for residual graph
capacity[(u, v)] = 0
capacity[(v, u)] = 0
capacity[(u, v)] += c # Handle multiple edges between same nodes
def bfs():
"""Find augmenting path using BFS. Returns (path, bottleneck)."""
parent = [-1] * (n + 1)
visited = [False] * (n + 1)
visited[1] = True
queue = deque([1])
while queue:
node = queue.popleft()
if node == n:
break
for neighbor in adj[node]:
if not visited[neighbor] and capacity[(node, neighbor)] > 0:
visited[neighbor] = True
parent[neighbor] = node
queue.append(neighbor)
# No path found
if parent[n] == -1:
return None, 0
# Reconstruct path and find bottleneck
path = []
bottleneck = float('inf')
current = n
while current != 1:
prev = parent[current]
path.append((prev, current))
bottleneck = min(bottleneck, capacity[(prev, current)])
current = prev
return path, bottleneck
total_flow = 0
while True:
path, bottleneck = bfs()
if path is None:
break
# Update residual capacities
for u, v in path:
capacity[(u, v)] -= bottleneck
capacity[(v, u)] += bottleneck
total_flow += bottleneck
return total_flow
def solve():
n, m = map(int, input().split())
edges = []
for _ in range(m):
a, b, c = map(int, input().split())
edges.append((a, b, c))
print(max_flow(n, edges))
if __name__ == "__main__":
solve()
Common Mistakes
1. Forgetting Reverse Edges
Wrong:
# Only adding forward edge
adj[u].append(v)
capacity[(u, v)] = c
Correct:
# Must add both directions for residual graph
adj[u].append(v)
adj[v].append(u)
capacity[(u, v)] = c
capacity[(v, u)] = 0 # Reverse edge starts with 0 capacity
2. Integer Overflow
With capacities up to 10^9 and potentially 500 * 1000 total flow:
- Maximum flow can be around 5 * 10^11
- Use
long longin C++ or Python’s arbitrary precision
Wrong (C++): Correct:
3. Not Handling Parallel Edges
Multiple edges between same nodes should have capacities added:
Wrong:
capacity[(u, v)] = c # Overwrites previous edge
Correct:
capacity[(u, v)] += c # Accumulates parallel edges
4. Incorrect Residual Update
Wrong:
# Only updating forward edge
capacity[(u, v)] -= bottleneck
Correct:
# Must update both directions
capacity[(u, v)] -= bottleneck # Reduce forward
capacity[(v, u)] += bottleneck # Increase backward
Complexity Analysis
| Operation | Complexity | Explanation |
|---|---|---|
| BFS | O(E) | Visits each edge once |
| Augmentations | O(VE) | Each edge saturates O(V) times with BFS |
| Total Time | O(VE^2) | O(VE) augmentations, each O(E) |
| Space | O(V^2) | Capacity matrix |
For this problem:
- V = 500, E = 1000
- O(VE^2) = O(500 * 1000^2) = O(5 * 10^8)
- Fits within time limit with efficient implementation
Key Takeaways
- Max flow = min cut: The maximum flow equals the minimum cut capacity
- Residual graph enables finding augmenting paths after initial flow
- Backward edges allow algorithm to “correct” suboptimal choices
- BFS guarantees O(VE^2) time (Edmonds-Karp)
- Handle large values with appropriate data types