DP over the subsets of a small ground set. The same technique as Bitmask DP — this page collects the patterns from the set point of view.

The state shapes

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

The three transition styles

1. Add one element —

for (int mask = 0; mask < (1 << n); mask++)
    for (int i = 0; i < n; i++)
        if (!(mask >> i & 1))
            dp[mask | (1 << i)] = min(dp[mask | (1 << i)], dp[mask] + cost(mask, i));

Use popcount(mask) to derive an index. In assignment problems, the number of elements already placed tells you which person/row is next — removing an entire dimension:

int i = __builtin_popcount(mask);            // next person to assign

2. Split into two —

for (int mask = 1; mask < (1 << n); mask++)
    for (int s = mask; s; s = (s - 1) & mask)
        dp[mask] = min(dp[mask], dp[s] + dp[mask ^ s]);

See Enumerating Submasks. Fix the lowest set bit in one part to break symmetry.

3. Aggregate over submasks —

When the inner sum is a plain subset sum, use SOS DP instead of a loop.

Symmetry breaking

The single most valuable optimisation. When grouping interchangeable items, force a canonical order:

int low = mask & -mask;                       // lowest remaining element
for (int s = mask; s; s = (s - 1) & mask)
    if (s & low) { /* this element leads its group */ }

Without it, a partition into groups is generated times. With it, once.

Worked patterns

Minimum groups covering a set

dp[0] = 0;
for (int mask = 1; mask < (1 << n); mask++) {
    int low = mask & -mask;
    for (int s = mask; s; s = (s - 1) & mask)
        if ((s & low) && valid[s])
            dp[mask] = min(dp[mask], dp[mask ^ s] + 1);
}

Assignment (min cost perfect matching)

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))
            dp[mask | (1<<j)] = min(dp[mask | (1<<j)], dp[mask] + cost[i][j]);
}

— better than the Hungarian algorithm for , and far easier to write.

Counting Hamiltonian paths

dp[mask][i] as in Held-Karp, summing instead of minimising.

Profile DP on a narrow grid

dp[row][mask] where mask describes the boundary — see Broken Profile DP.

Complexity limits

ShapeFeasible
25
22
18-20
16-17
memory~18 (memory usually binds first)

When is too big

  • Split the set in half → meet in the middle, .
  • Does the set have structure (an interval, a prefix, a tree)? Then the “subset” dimension may collapse.
  • Are the items interchangeable? Then a count, not a set, suffices — states instead of .
  • Accept an approximation → simulated annealing.

That third point is the most commonly missed: if all items of a given type are identical, replace “which subset is used” with “how many of each type”, turning into a product of small counts.

See also: Bitmask DP · SOS DP · Enumerating Submasks