3.7.15 · D2Algorithm Paradigms

Visual walkthrough — Bitmask DP — TSP intro

2,238 words10 min readBack to topic

We use one running example the whole way down. Four cities on a plane:

with symmetric distances (edge costs the same as ):

Here just means "the number written on the road between city and city ." Nothing more.


Step 1 — Draw the world we are optimising

WHY start by drawing? Because every abstract symbol below (, , "current city") is a fact about this map. If you can point at it on the map, you understand it. If you can't, you don't.

PICTURE. The four cities are dots; the ten roads are gray with their costs. A tour is a loop that touches every dot once. There are genuinely different loops here — few enough to check by hand later, which is exactly why 4 cities is a good teaching size.

Figure — Bitmask DP — TSP intro

Step 2 — Turn "which cities are done" into a single integer

WHY integers and not, say, a Python set? Because our table needs an index — a slot number. An integer is the cheapest slot number that exists. The set becomes the switch row 0011, which as an ordinary number is . So "the set of visited cities" and "the number " are the same object seen two ways.

PICTURE. A strip of four switches. Below each switch we write its value. The visited set lights switches and , giving 0101 .

Figure — Bitmask DP — TSP intro

See Bit Manipulation if any of these three lines feels shaky — they are the entire vocabulary we need.


Step 3 — Name the table cell: dp[mask][i]

WHAT. One number per (switch-row, current-city) pair. A grid: rows are the switch-rows, columns are the possible "current city" values.

WHY this shape? This is the whole magic of the parent note. A path is a long story (). But the only two facts that affect what happens next are the SET already done and the SPOT you're on. Every story with the same (SET, SPOT) is interchangeable from here — so we keep just the cheapest and forget the rest. That forgetting is what shrinks down to a grid of size .

PICTURE. A blank grid. Most cells are impossible and painted gray: dp[mask][i] only makes sense if city 's switch is ON in (you can't "end at " if you never visited ). Look — exactly half-ish of the grid is legal.

Figure — Bitmask DP — TSP intro

Step 4 — Plant the seed: the base case

WHAT. Set exactly one legal cell to ; leave all others .

WHY ? You start at city having spent nothing and visited nobody but yourself. The switch row is 0001 (only switch lit) . Cost so far: roads walked .

PICTURE. The grid from Step 3 with a single green dropped into row 0001, column . Every other cell is still . This lone seed is the ancestor of every number we compute after it.

Figure — Bitmask DP — TSP intro

Step 5 — The one rule: grow a path by its last edge

Now the engine. To fill a cell dp[mask][i], ask: who did I visit right before ?

WHY is this allowed? Optimal substructure. Any cheapest path ending at has some last-visited city . Chop off that final road ; the leftover prefix must itself be a cheapest path to — if a cheaper prefix existed we'd glue it on and beat our own minimum, a contradiction. This is the same logic that makes all Dynamic Programming legal.

PICTURE. One target cell (orange), fed by arrows from the cells one switch smaller. We are literally peeling the last edge and reading the already-filled predecessors. This is Held-Karp Algorithm in a single frame.

Figure — Bitmask DP — TSP intro

Step 6 — Run the engine on our map, layer by layer

WHAT. Fill cells in order of how many switches are lit — because a cell only ever depends on cells with fewer switches. So process popcount , then , then , then . (popcount = how many switches are ON.)

WHY this order? It guarantees every we read on the right-hand side was already finalised on a previous layer. No cell is used before it's ready.

PICTURE — the numbers appearing. Watch each layer light up (recall dist: ):

  • Layer 1 (seed): dp[0001][0]=0.
  • Layer 2 — one hop out of :

  • Layer 3 — two cities besides , e.g. set , end at :

Here only is legal (we need and ; would need dp[0101][0] which doesn't reach through ... careful: , so ):

But dp[0011][0] is (you can't stand at with visited via this DP's forward growth reaching ), so the live term is . ✓

Figure — Bitmask DP — TSP intro

Step 7 — Fill the full layer, then close the tour

WHAT. Compute the three cells dp[1111][1], dp[1111][2], dp[1111][3], add the road home, take the smallest.

For our numbers the optimal closed tour is :

WHY add ? A tour is a cycle (see Hamiltonian Path and Cycle). The DP only walked out to everyone; it never paid to come back. Forgetting this return road is trap #3 in the parent. If you wanted a Hamiltonian path instead of a cycle, drop this term and answer .

PICTURE. The three full-layer cells with their home-roads drawn as dashed arrows back to ; the winning highlighted, its actual loop traced on the map.

Figure — Bitmask DP — TSP intro

Step 8 — Edge & degenerate cases (never leave a hole)

PICTURE. Four tiny grids side by side: (single seed = answer), (two-cell chain), an asymmetric arrow pair, and a broken road drawn as a red that the skips.

Figure — Bitmask DP — TSP intro

The one-picture summary

Everything above, compressed: the seed at bottom, arrows flowing up the layers as switches turn on one at a time (each arrow = ", keep the min"), and the dashed home-roads at the top closing the cycle. The whole Held-Karp Algorithm is water flowing uphill through a lattice of switch-rows, then falling home.

Figure — Bitmask DP — TSP intro
Recall Feynman: the whole walkthrough in plain words

I drew four houses and the roads between them (Step 1). Instead of remembering full walks, I put four light switches on a sticky note — one per house — and flipped a switch on when I visited that house; the whole row of switches read as a single number is the "mask" (Step 2). My scratch grid has one box for every (switch-row, house-I'm-in-now) pair, and boxes claiming I ended in a house whose switch is off are nonsense, so I gray them out (Step 3). I dropped a single in the box "only-home-switch-on, standing-at-home" — the seed of everything (Step 4). My one rule: to fill any box, I peel off the last road I walked, look up the already-filled box for "one house earlier," and add that road's cost, keeping only the cheapest predecessor (Step 5). I filled boxes in waves — one switch on, then two, then three, then all four — so every box I read was already done (Steps 6–7). Once all switches were on I paid one last road back home and took the smallest total: (Step 7). Finally I checked the weird cases — one city, two cities, one-way roads, broken roads — so nothing can surprise me (Step 8).

Recall

In dp[mask][i], what does mask physically mean and why an integer? ::: The set of already-visited cities, stored as light switches (bit i on ⟺ city i visited). It's an integer because the DP table needs a cheap index. Why fill cells in increasing popcount order? ::: Each cell depends only on cells with one fewer switch on, so processing by number-of-switches guarantees predecessors are ready. Why the final +dist[i][0]? ::: A tour is a cycle; the DP only walked outward, so we pay one last road home. Drop it for a Hamiltonian path. What is the optimal tour cost in the running example? ::: 18, via 0→1→3→2→0.


Connections