AlgoMaster Logo

Campus Bikes II

mediumFrequencyUpdated September 21, 2026

Understanding the Problem

We need a one-to-one assignment of bikes to workers that minimizes the total Manhattan distance. Each worker gets exactly one bike, and since m >= n, some bikes may go unassigned.

This is an assignment problem. Brute force tries every way of distributing bikes over workers. A better approach follows from one fact: if we assign workers in a fixed order (worker 0, then worker 1, and so on), future decisions depend only on which bikes are already taken, not on which worker took which bike. That set of taken bikes fits in a bitmask, and with m at most 10 there are at most 2^10 = 1,024 possible masks, so the entire state space is small enough to enumerate. This is the setup for bitmask dynamic programming.

Key Constraints:

  • 1 <= n <= m <= 10 → Brute force explores at most m!/(m-n)! assignments, which is 10! ≈ 3.6 million in the worst case. Bitmask DP cuts this to about m * 2^m ≈ 10,000 operations.
  • 0 <= workers[i][0], workers[i][1] < 1000 → A single Manhattan distance is under 2,000 and the total is under 20,000, so a 32-bit integer is more than enough.

Approach 1: Brute Force (Backtracking)

Intuition

Try every possible assignment: for worker 0, try each bike; for worker 1, try each remaining bike; continue until every worker has one. Track which bikes are taken in a boolean array and keep a running total of distances. When all workers are assigned, compare the total against the best one found so far.

This is backtracking over a decision tree where each level corresponds to a worker and each branch corresponds to a bike choice. Branches whose running total already matches or exceeds the best answer cannot improve it, so we prune them.

Algorithm

  1. Recurse over workers in order, starting with worker 0, an empty set of used bikes, and a running total of 0.
  2. If every worker has been assigned, update the global minimum with the running total and return.
  3. If the running total already meets or exceeds the best total found so far, return without exploring further (prune).
  4. Otherwise, for each unused bike: mark it used, recurse for the next worker with the running total increased by this worker-bike distance, then unmark it.
  5. After the recursion finishes, return the global minimum.

Example Walkthrough

For workers = [[0,0],[2,1]] and bikes = [[1,2],[3,3]], the pairwise distances are: worker 0 to bike 0 = 3, worker 0 to bike 1 = 6, worker 1 to bike 0 = 2, worker 1 to bike 1 = 3. The first branch assigns bike 0 to worker 0 and bike 1 to worker 1 for a total of 3 + 3 = 6, which becomes the best answer. The second branch assigns bike 1 to worker 0 for a running total of 6; the recursive call for worker 1 prunes immediately because 6 already equals the best total, so the full cost of that branch (6 + 2 = 8) is never computed. The function returns 6.

Code

The backtracking approach reaches the same subproblem state through different orderings and re-solves it each time. The next approach caches the result for each state so no state is solved twice.

Approach 2: Bitmask Dynamic Programming (Top-Down)

Intuition

Backtracking repeats work. If workers 0 and 1 hold bikes 2 and 5, the cheapest way to finish is the same whether worker 0 took bike 2 or bike 5: the remaining workers and remaining bikes are identical in both cases. The state that matters is the set of taken bikes, stored as an integer where bit j is set when bike j is assigned. Because workers are assigned in order, the next worker's index equals the number of set bits, so the mask alone encodes the full state.

Define solve(mask) as the minimum cost to assign all remaining workers, given that the bikes in mask are taken. This value depends only on the mask, never on how the earlier pairings were made, so memoizing it per mask is valid. That replaces a factorial-sized search tree with a table of 2^m entries, each computed at most once.

Algorithm

  1. Define a recursive function solve(mask):
    • Count the number of set bits in mask. This is the current worker index.
    • If all workers are assigned (set bits == n), return 0.
    • If the result for mask is memoized, return it.
    • Otherwise, for each bike j not in the mask, the cost of giving it to the current worker is their Manhattan distance plus solve(mask | (1 << j)).
    • Memoize and return the minimum cost across all bike choices.
  2. The answer is solve(0) (no bikes assigned yet).

Example Walkthrough

For workers = [[0,0],[2,1]] and bikes = [[1,2],[3,3]], the pairwise distances are d[0][0]=3, d[0][1]=6, d[1][0]=2, d[1][1]=3. The animation follows the recursion through both branches and shows the memo table filling in.

distances
1Distance table: d[worker][bike] = Manhattan distance
d[0][0]
:
3
d[0][1]
:
6
d[1][0]
:
2
d[1][1]
:
3
memo
1Memo table empty. Start solve(mask=00)
1/7

Code

The same state space can also be processed iteratively, which removes the recursion and its function call overhead.

Approach 3: Bitmask Dynamic Programming (Bottom-Up)

Intuition

Instead of recursing from the empty mask, iterate through all masks from 0 to 2^m - 1 and push costs forward. Define dp[mask] as the minimum cost to assign the bikes in mask to the first bitCount(mask) workers, with dp[0] = 0. The meaning flips relative to Approach 2: solve(mask) was the cost to finish from a state, while dp[mask] is the cost to reach it.

Iteration order is what makes this correct. Every transition goes from mask to mask | (1 << j), which is a strictly larger number, so processing masks in increasing numeric order guarantees dp[mask] holds its final value before it is used to update any successor.

Algorithm

  1. Initialize a DP array of size 2^m, filled with infinity. Set dp[0] = 0.
  2. Iterate through each mask from 0 to 2^m - 1.
  3. For each mask, compute workerIdx = bitCount(mask).
  4. If workerIdx >= n, skip (all workers already assigned). Also skip masks still at infinity, so we never add to an unreached state.
  5. For each bike j not in the mask:
    • Compute newMask = mask | (1 << j).
    • Update dp[newMask] = min(dp[newMask], dp[mask] + dist), where dist is the Manhattan distance between worker workerIdx and bike j.
  6. The answer is the minimum dp[mask] across all masks with exactly n bits set. When m > n, the unassigned bikes leave bits unset, so there is no single final mask; any mask with n bits could be the end state.

Example Walkthrough

For workers = [[0,0],[2,1]] and bikes = [[1,2],[3,3]], m = 2, so the dp array has 2^2 = 4 entries, indexed by mask. The animation processes the masks in increasing order:

1Initialize: dp[0]=0 (no bikes assigned), rest=INF
0
0
mask=00
1
INF
2
INF
3
INF
1/6

Code