Project Euler Problem 181

Having three black objects B and one white object W they can be grouped in 7 ways like this: | | | | | | | | |--------|-

Project Euler Problem 181

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:

  • i black objects,
  • j white 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