Chapter 9 · Hardness and Escape Routes
Escape Routes: What To Do When It Is NP-Hard
Small n, meet in the middle, branch and bound, FPT by a parameter, and approximation — with the running-time arithmetic for each.
Five techniques, ranked by how the constraints tell you which one is intended.
#1. DP over subsets — n ≤ 22
dp[M][v]: 2n · n states, O(n) (or O(deg)) each.
for (int M = 1; M < (1 << n); M++)
for (int v = 0; v < n; v++) if (M >> v & 1) {
int P = M ^ (1 << v);
for (int u = 0; u < n; u++) if (P >> u & 1 && adj[v][u])
dp[M][v] = min(dp[M][v], dp[P][u] + w(u, v));
}Arithmetic to check first: 222 · 22 ≈ 9 · 107 states — fine; 226 · 26 ≈ 1.7 · 109 — too slow and memory 226 · 26 bytes = 1.7 GB, too big. Memory is usually the binding constraint: iterate masks and keep only dp[mask][v] as int (4 bytes), or drop the [v] dimension entirely when the transition depends only on M (then O(2n) memory).
Variants of the same 8 lines
- Hamiltonian path/cycle and TSP (Held–Karp: Hamilton in O(2ⁿn²)),
- assignment problem with n ≤ 20 (faster than Hungarian for tiny n),
- "minimum number of groups with a constraint on each" — iterate subsets,
dp[M] = min over sub ⊆ M, which is O(3n) total: enumerate submasks withfor (sub = M; sub; sub = (sub-1) & M), - graph colouring in O(2n n): precompute which subsets are independent, then "cover V by ≤ k independent sets" = subset DP, or use inclusion–exclusion for the chromatic polynomial,
- Steiner tree: dp[M][v] over terminal subsets with Dijkstra between layers, O(3t n + 2t n log n).
#2. Meet in the middle — n ≤ 44
Split into two halves, enumerate 2n/2 subsets each, then combine by sorting + two pointers or binary search.
// subset sum to target T with n <= 40
vector<ll> A, B; // all 2^(n/2) subset sums of each half
sort(B.begin(), B.end());
ll best = -1;
for (ll a : A) {
auto it = lower_bound(B.begin(), B.end(), T - a);
if (it != B.end() && a + *it == T) { best = T; break; }
}2 · 220 = 2 · 106 elements instead of 240 — the entire trick is that a sorted list can be searched, so pairs become cheap.
#3. Branch and bound / backtracking with a real prune — n ≤ 40–60 on nice tests
The prunes that matter, in order of value:
- 1
order the branching so that the strongest constraint is decided first (largest degree, smallest domain);
- 2
bound with a fast relaxation (LP-free: fractional knapsack bound, MST bound for TSP, greedy colour count) and cut the subtree when the bound cannot beat the incumbent,
- 3
forward checking: after assigning, remove impossible values; fail immediately on an empty domain,
- 4
memoise on a canonical state (a bitmask of decided vertices) — the moment you memoise, branch and bound becomes DP and the bound stops mattering,
- 5
restart / randomise the order if a fixed order blows up on one adversarial test.
Branch on a vertex v: either take it (delete N[v]) or forbid it (delete v). Recurrence T(n) = T(n-1) + T(n-d-1); with d ≥ 2 that is ≈ 1.32n. Measure-and-conquer analyses push it to 1.1996n by handling low-degree vertices with dedicated rules — which is exactly what the "branch on the degree-2 vertex and contract it" line in real code is doing.
#4. FPT in a parameter you noticed
"Exponential in k, polynomial in n" — kernelise then branch:
- vertex cover: bounded search tree O(2k k), or O(1.2738k + kn) with branching rules on degree-3+ vertices; LP relaxation gives a 2k-vertex kernel,
- treewidth k: solve any MSO/DP problem in O(f(k) n) once you have a tree decomposition (and for planar graphs, k = O(√n), so "exponential in √n" is a real planar algorithm),
- dominating set on planar graphs: O(2O(√n) n) via the same,
- "at most k edges to delete to become bipartite": iterative compression or O(4k · n).
#5. Approximation and randomisation
Provably-good answers you can code in 5 lines
- maximal matching → 2-approx for vertex cover and for maximum matching (The Extremal Principle),
- random partition, keep the better side → ≥ m/2 edges in a bipartite subgraph; derandomise by greedy flipping, which is the same bound,
- Christofides for metric TSP: MST + minimum perfect matching on the odd-degree vertices (3/2) — uses General Matching and Blossoms as a subroutine, which is why that page exists,
- local search "improve by swapping 2" for TSP: O(1)-factor in practice, no proof,
- simulated annealing / random restarts for "output any good answer" scoring problems: measure your own bound rather than trusting folklore.
Read n and k and compute. 2n fits → subset DP. 2n/2 fits → meet in the middle. n huge but k ≤ 20 → FPT/branch. n huge, no parameter, "output any" → approximation with a proof you can state. If none of the four fit, re-read the statement: you have mis-modelled the problem, which is by far the most common reason.