Bitmasking and DP
Given n people, each willing to wear only certain caps drawn from a shared pool of cap types, count the number of ways to give every person a different cap, using only caps that person is willing to wear.
Do this lesson first: coin changeExample input
likes = [[1, 2, 5], [1, 3, 4], [2, 3, 6]] (person 0 wears caps 1, 2 or 5, and so on), cap types 1 to 4
Expected output
5
Break it down
Answer each question out loud before you open it. Getting it wrong here is the useful part. A revealed answer you never guessed at teaches you nothing.
Fill the table
The table pauses before each cell you have to supply. Type the value the recurrence gives, and the write animation confirms it.
Press start. The animation stops at every cell YOUR recurrence must fill.
Row 0 is given: with no cap types dealt out yet, the only possible group is nobody, so dp[0][000] = 1, the empty assignment, and every other dp[0][mask] = 0. Every later cell reads only the row directly above it, so filling top to bottom is enough, and within a row the order does not matter. The answer is the bottom-right cell, every cap type considered and every person covered. Cells for partial groups are not wasted: dp[3][101], persons 0 and 2 covered by caps 1 to 3, is exactly what row 4 reads when cap 4 goes to person 1.
Cols →
Rows ↓c: cap types 1 to c have been dealt with
Cols →mask: who is already wearing a cap, bit p for person p
- 1
function capWays(likes, caps) { - 2
const n = likes.length, FULL = (1 << n) - 1; - 3
const dp = []; - 4
dp[0] = Array.from({ length: FULL + 1 }, (_, m) => (m === 0 ? 1 : 0)); - 5
for (let c = 1; c <= caps; c++) { - 6
dp[c] = []; - 7
for (let mask = 0; mask <= FULL; mask++) { - 8
let ways = dp[c - 1][mask]; // cap c stays on the shelf - 9
for (let p = 0; p < n; p++) - 10
if (mask & (1 << p) && likes[p].includes(c)) - 11
ways += dp[c - 1][mask & ~(1 << p)]; // cap c goes to person p - 12
dp[c][mask] = ways; - 13
} - 14
} - 15
return dp[caps][FULL]; - 16
}
The code, the trap, the variations
- 1
function capWays(likes, caps) { - 2
const n = likes.length, FULL = (1 << n) - 1; - 3
const dp = []; - 4
dp[0] = Array.from({ length: FULL + 1 }, (_, m) => (m === 0 ? 1 : 0)); - 5
for (let c = 1; c <= caps; c++) { - 6
dp[c] = []; - 7
for (let mask = 0; mask <= FULL; mask++) { - 8
let ways = dp[c - 1][mask]; // cap c stays on the shelf - 9
for (let p = 0; p < n; p++) - 10
if (mask & (1 << p) && likes[p].includes(c)) - 11
ways += dp[c - 1][mask & ~(1 << p)]; // cap c goes to person p - 12
dp[c][mask] = ways; - 13
} - 14
} - 15
return dp[caps][FULL]; - 16
}
Where people go wrong
Putting the caps in the mask. It is a common first attempt, because the caps are what gets used up, and it works on toy inputs, but a mask over 100 cap types has 2^100 values and no table will ever hold it. The rule to carry away: the mask goes on whichever set is small, and here that is the people. The other common slip is skipping the table and multiplying each person's options. With caps 1 to 4 that is 2 × 3 × 2 = 12, against the true 5, because the product happily gives cap 1 to persons 0 and 1 at once.
Drop the rule that caps must differ, so two people may wear the same type.
The mask stops being needed at all. With no clash to avoid, people choose independently and the answer is the product of how many caps each one likes, 12 for caps 1 to 4. The question and the inputs are unchanged; what disappears is the reason for the mask, which only existed because each cap type goes to one person at most, and that forces the table to remember who is covered.
Each cap type comes in k copies, so up to k people can wear the same type.
The state is unchanged. The transition widens: instead of giving cap c to one person in mask, give it to any group of at most k people in mask who all like it, and add dp[c - 1][mask without that group] for each such group. The row-by-row order is unchanged.
Add a fourth person.
The table doubles in width to 16 masks, past this page's 12-column budget, which is why it stops at three people. The method itself is unchanged. The cost grows exponentially with the people and only linearly with the caps: every extra person doubles the row, so 20 people means rows of 2^20, about a million cells, while every extra cap type adds one more row.