The state contains a subset of a small ground set (–), stored as an integer. Bit tricks and submask enumeration are in Bit Tricks and Submasks and SOS; Held–Karp and Hamiltonian paths are in Hamiltonian Paths.

WorkFeasible
25–27
20–23
17–19
15–17

Transition shapes

// 1. add one element: assignment, person popcount(mask) gets a job          O(2^n n)
for (int mask = 0; mask < 1 << n; mask++) {
    int i = __builtin_popcount(mask); if (i == n) continue;
    for (int j = 0; j < n; j++) if (!(mask >> j & 1)) chmin(dp[mask | 1 << j], dp[mask] + cost[i][j]);
}
// 2. split off a group containing the lowest element: set partition / cover O(3^n)
for (int mask = 1; mask < 1 << n; mask++) {
    int low = mask & -mask;
    for (int s = mask; s; s = (s - 1) & mask) if ((s & low) && ok[s]) chmin(dp[mask], dp[mask ^ s] + 1);
}
// 3. aggregate over all submasks: SOS / zeta transform                     O(2^n n)

Classic problems

  • Assignment (): pattern 1. Simpler than Hungarian (Assignment) for small .
  • TSP / Hamiltonian path (): dp[mask][i] = shortest path visiting exactly mask, ending at , — the extra over pattern 1 comes from also tracking the current endpoint (Hamiltonian Paths).
  • Counting perfect matchings of a bipartite graph (the permanent of a 0/1 matrix): pattern 1 with sums, or Ryser’s formula in with Gray code.
  • Elevator rides (minimum rides of capacity carrying everyone): dp[mask] = (rides, load of the last ride), minimised lexicographically; add person to the last ride if it fits, else open a new ride. .
  • equal-sum groups: same shape as elevator rides with load modulo target; feasible iff dp[full] reaches load 0.
  • Graph colouring / chromatic number: inclusion-exclusion over independent sets in (Graph Coloring).
  • Maximum independent set / clique for –: branch on max-degree vertices or meet in the middle (Maximum Clique).
  • Order-dependent costs (“the cost of placing item depends on which items are already placed”): pattern 1 with cost(mask, j), often precomputed per mask in .
  • Subset sums of a mask (sum[mask]) in : sum[mask] = sum[mask & (mask - 1)] + a[ctz(mask)].

Reducing the state

A compact reference for the shapes these states take:

StateMeaningExample
dp[mask]best/count for the set maskset cover, partitioning
dp[mask][i]set handled, currently at TSP
dp[mask][j]set handled, extra resourceassignment with budgets
dp[i][mask]first items, chosen set masksmall-item knapsack
dp[mask] = pair(count of full groups, current partial) equal-sum partitions
  • Derive an index from popcount (the next person, the current position).
  • Interchangeable elements: store counts per type instead of a set; states instead of .
  • Profile instead of set: when only a frontier matters (grids), see Profile DP.
  • Layer by popcount to save memory: only masks of size and are alive.
  • Split the ground set and combine with meet in the middle or SOS: e.g. count subsets of items with a property that decomposes.
  • Flatten to a 1D array (vector<int> indexed by mask, not vector<vector<int>>): noticeably better cache behaviour at + states.
  • Trade time for space: int is 80 MB, often over the limit; the inclusion-exclusion reformulation (see below) swaps this for time and only memory when the DP admits one.
  • Accept an approximation when is simply too big for any exact exponential method: simulated annealing or beam search.

Exponential DPs beyond

  • Subset convolution / set power series (, , over subsets) in : counting connected spanning subgraphs, partitions into connected parts (SOS).
  • Inclusion-exclusion over subsets replaces “exactly the full set” by alternating sums of “within ”: counting Hamiltonian cycles in time and polynomial space, Steiner tree, and set cover counting.
  • DP over subsets of a separator / tree decomposition: for dominating set, for independent set with treewidth (Special Graphs).