Project Euler Problem 181
Having three black objects B and one white object W they can be grouped in 7 ways like this: | | | | | | | | |--------|-
Solution
Answer: 83735848679360680
Let a group be described only by how many black and white objects it contains.
So a group type is a pair
$$(b,w)\neq(0,0)$$
meaning the group contains $b$ black objects and $w$ white objects.
We must count the number of multisets of such groups whose total is
$$(60,40).$$
For example, with $3$ black and $1$ white object, the seven possibilities are exactly the seven multisets of pairs summing to $(3,1)$.
1. Mathematical analysis
This is a 2-dimensional partition problem.
Generating function
For every possible group type $(b,w)\neq(0,0)$, we may use that type:
- zero times,
- one time,
- two times,
- etc.
Hence its contribution to the generating function is
$$1+x^b y^w+x^{2b}y^{2w}+\cdots = \frac1{1-x^b y^w}.$$
Multiplying over all possible nonzero pairs gives
$$F(x,y) = \prod_{(b,w)\neq(0,0)} \frac1{1-x^b y^w}.$$
The required answer is the coefficient of
$$x^{60}y^{40}.$$
Dynamic programming interpretation
This is exactly analogous to the classic integer partition DP (“coin change”), except now each “coin” has two coordinates.
Define
$$dp[i][j]$$
to be the number of ways to form totals $(i,j)$.
Initially:
$$dp[0][0]=1.$$
For every group type $(b,w)\neq(0,0)$, update:
$$dp[i+b][j+w] += dp[i][j].$$
The loops must go forward so that a group type may be reused arbitrarily many times.
Since we only need totals up to $(60,40)$, we only consider
$$0\le b\le 60,\qquad 0\le w\le 40,$$
excluding $(0,0)$.
The complexity is easily manageable.
2. Python implementation
# Project Euler 181
# Count ways to group 60 black objects and 40 white objects.
B = 60
W = 40
# dp[i][j] = number of ways to form i black and j white objects
dp = [[0] * (W + 1) for _ in range(B + 1)]
# Empty grouping
dp[0][0] = 1
# Process every possible nonempty group type (b, w)
for b in range(B + 1):
for w in range(W + 1):
# Skip the empty group
if b == 0 and w == 0:
continue
# Forward iteration => unlimited reuse allowed
for i in range(B - b + 1):
for j in range(W - w + 1):
dp[i + b][j + w] += dp[i][j]
print(dp[B][W])
3. Code walkthrough
Step 1 — Create the DP table
dp = [[0] * (W + 1) for _ in range(B + 1)]
dp[i][j] stores the number of valid groupings using exactly:
iblack objects,jwhite objects.
Step 2 — Base case
dp[0][0] = 1
There is one way to group nothing: use no groups.
Step 3 — Iterate over all group types
for b in range(B + 1):
for w in range(W + 1):
Each pair (b,w) represents one possible group composition.
We exclude (0,0) because empty groups are not allowed.
Step 4 — Transition
dp[i + b][j + w] += dp[i][j]
If we can form (i,j), then by adding one more group (b,w) we can form (i+b,j+w).
The loops go forward:
for i in range(B - b + 1):
for j in range(W - w + 1):
which allows unlimited reuse of the same group type.
Verification on the sample
Running the same algorithm for $B=3$, $W=1$ gives:
$$7$$
matching the problem statement exactly.
4. Final verification
The count should be extremely large because:
- there are many possible group types,
- repetitions are unrestricted,
- this is essentially a 2D partition function.
The computed value is:
$$83{,}735{,}848{,}679{,}360{,}680$$
which is of the expected magnitude.
Answer: 83735848679360680