Hints — Module 22
Graduated hints. Read only as far as you need. ./grade 22 --stage K --hint J.
Stage 1: Hamiltonian paths by backtracking
- This is Algorithm 7.2.2B (basic backtrack) with one change: the "property test" that admits a candidate is unvisited AND adjacent to the current endpoint. The partial solution is a simple path; the level is its length.
- Build the adjacency matrix once so "is u adjacent to v?" is O(1). Recurse
with a
path: Vec<usize>and aused: Vec<bool>; on backtrack,path.pop()and clear theusedflag — restore the earlier state exactly. A path may start anywhere (loop over all starts); a cycle may fix start 0. - For the count, tally directed closed walks from vertex 0 and divide by 2
(each undirected cycle runs in two directions). Cycle acceptance at level n:
matrix[last][start]. ReturnNone/0whenn < 3for cycles.fn extend(mat, start, path, used, want_cycle) -> bool:if path.len()==n: return !want_cycle || mat[last][start]for v in 0..n where !used[v] && mat[last][v]: try v, recurse, undo
Stage 2: Warnsdorff's heuristic — the knight's tour
- The board graph: square
sq = row*board + col; edges are the eight(±1,±2)/(±2,±1)knight offsets that land on the board. Warnsdorff is a greedy rule — no backtracking — so it may returnNone. knight_movesshould emit destinations in a fixed offset order; that fixed order is your deterministic tie-break. At each step, score each unvisited neighborvby how many unvisited neighbors it has, and move to the minimizer.- Break ties by smaller square index: pick the
vminimizing the pair(onward_degree, v). If there is no unvisited neighbor, the walk is stuck → returnNone.is_valid_tour: lengthboard², a permutation, and everywindows(2)pair is a legal knight move.deg(v) = knight_moves(board,v).filter(|s| !visited[s]).count()next = argmin over unvisited neighbors of (deg(v), v)
Stage 3: Hamiltonian cycles on the hypercube are Gray codes
Q_dhas the2^dd-bit strings as vertices; two are adjacent iff they differ in one bit. A Hamiltonian cycle onQ_dis exactly a cyclic Gray code — the reflected codeg(k) = k ⊕ ⌊k/2⌋from Module 08.hypercube_neighbors(d, v): XORvwith each1<<iforiin0..d.gray_code_cycle(d): mapk -> k ^ (k>>1)overkin0..(1<<d).- The validator must check the cyclic single-bit property, i.e. include the
wrap-around from the last vertex to the first:
all 2^d vertices distinct and in range, ANDfor i in 0..n: (cycle[i] ^ cycle[(i+1) % n]).count_ones() == 1
Stage 4: Held–Karp — shortest Hamiltonian path by bitmask DP
- State
C(S, j)= least-weight path visiting exactly vertex setSand ending atj ∈ S. Recurrence:C(S,j) = min_{i∈S\{j}} C(S\{j}, i) + d(i,j), baseC({j}, j) = 0. The subsetSis a bitmask (Module 13). - Use a
dp[1<<n][n]table filled by iterating subsetsSin increasing integer order (valid because adding a bit only growsS). Use anINFsentinel likeu64::MAX/4soINF + weightnever overflows. - Push form: from a reachable
(S, j), extend to eachk ∉ Sviadp[S|1<<k][k] = min(…, dp[S][j] + d[j][k]). Path answer:min_j dp[full][j]. Cycle: fix start 0 (dp[1][0]=0, never revisit 0 mid-tour), thenmin_{j≠0} dp[full][j] + d[j][0]. Return 0 forn ≤ 1.