Problem 88 — Product-sum Numbers

solved Fri, 18 Jul 2025, 21:21 · level 4 · difficulty 13% projecteuler.net github

A natural number, $N$, that can be written as the sum and product of a given set of at least two natural numbers, $\{a_1, a_2, \dots, a_k\}$ is called a product-sum number: $N = a_1 + a_2 + \cdots + a_k = a_1 \times a_2 \times \cdots \times a_k$.

For example, $6 = 1 + 2 + 3 = 1 \times 2 \times 3$.

For a given set of size, $k$, we shall call the smallest $N$ with this property a minimal product-sum number. The minimal product-sum numbers for sets of size, $k = 2, 3, 4, 5$, and $6$ are as follows.

  • $k=2$: $4 = 2 \times 2 = 2 + 2$
  • $k=3$: $6 = 1 \times 2 \times 3 = 1 + 2 + 3$
  • $k=4$: $8 = 1 \times 1 \times 2 \times 4 = 1 + 1 + 2 + 4$
  • $k=5$: $8 = 1 \times 1 \times 2 \times 2 \times 2 = 1 + 1 + 2 + 2 + 2$
  • $k=6$: $12 = 1 \times 1 \times 1 \times 1 \times 2 \times 6 = 1 + 1 + 1 + 1 + 2 + 6$

Hence for $2 \le k \le 6$, the sum of all the minimal product-sum numbers is $4+6+8+12 = 30$; note that $8$ is only counted once in the sum.

In fact, as the complete set of minimal product-sum numbers for $2 \le k \le 12$ is $\{4, 6, 8, 12, 15, 16\}$, the sum is $61$.

What is the sum of all the minimal product-sum numbers for $2 \le k \le 12000$?


Problem Analysis

A product-sum number for a set of size k is a natural number N that can be expressed simultaneously as the product and the sum of a multiset of k natural numbers. I must find, for every k from 2 to 12000, the smallest such N - its minimal product-sum number - and then sum the distinct values found (the example reminds us that 8 serves several values of k but counts once). The naive reading of the problem - search over all multisets of size k - is hopeless, since the count of multisets grows exponentially in k. The whole problem turns on a single algebraic observation about the behaviour of the number 1.

The number 1 is the multiplicative identity: multiplying by 1 leaves a product unchanged but adding 1 raises a sum by 1. So given any multiset of "real" factors (each at least 2) with product P and sum S, where P ≥ S, I can append exactly P - S ones. The product stays P, the sum rises to S + (P - S) = P, and the two now agree. The padded set has size k = m + (P - S), where m is the number of real factors. This means I never enumerate the ones at all: every integer factorization into factors ≥ 2 implicitly defines one candidate (k, N) pair. The search collapses from "all multisets" to "all factorizations," which is dramatically smaller. A second observation bounds the search: for any k, the set {2, k, 1, 1, ..., 1} (with k - 2 ones) has product 2k and sum 2 + k + (k - 2) = 2k, so it is always valid. Hence the minimal N for any k never exceeds 2k. That caps every value at 2 · max_k, gives the answer table a safe default, and drives a branch-and-bound pruning rule that keeps the work close to linear in max_k.

Solution Approach

Recursive Factorization Search

Both files implement the same algorithm, so I describe it once and note the language differences afterward. I keep a table min_prod[k] initialised to 2 * max_k - the universal upper bound - for every k. The recursive routine find_product_sum(prod, total, count, start) represents the state of having chosen count factors so far with running product prod and running sum total; start is the smallest value the next factor may take.

At each call I compute k = prod - total + count. Here prod - total is the number of ones needed to make the sum equal the product, and count is the number of factors already chosen, so k is exactly the implied set size from the padding identity. If k is within range I relax the table with min_prod[k] = min(min_prod[k], prod). I then extend the factorization by iterating the next factor i from start upward, recursing with updated product, sum, and count, and passing i as the new start so factors stay non-decreasing.

The loop upper limit is max_k // prod * 2 + 1. If prod * i exceeded 2 * max_k, the resulting N would beat our bound for every k, so there is no reason to continue. Because this limit is recomputed at each level using the current prod, it tightens as the product grows, cutting branches more aggressively the deeper the recursion descends.

The initial call is find_product_sum(1, 1, 1, min_k). Starting product and sum at 1 with count 1 is a bookkeeping seed: when the first real factor (say 2) is chosen, the product becomes 2, the sum becomes 3, and count becomes 2 - but the count-1 seed cancels correctly in the k formula so single real-factor cases score properly. Setting start = min_k (which is 2) guarantees every chosen factor is at least 2, so I explore only genuine factorizations and never spuriously include a 1 as a "real" factor.

After the search I sum the distinct table entries. The Python version uses the concise sum(set(min_prod[2:])). The C version, lacking a built-in set, allocates a char array of size 2 * max_k + 1 indexed by value, marks each value as seen on first encounter, and accumulates only unseen values - an O(N) deduplication. The C code also uses two file-scope globals, g_max_k and g_min_prod, to avoid threading shared state through every recursive call, whereas Python defines the helper as a nested function that closes over the enclosing variables. On the main case (max_k = 12000) the C build runs in about 0.0011 s versus 0.0556 s for Python, roughly a 50x gap.

workbench

Test cases

categoryinputanswer
dev max_k = 6 min_k = 2 30
main max_k = 12000 min_k = 2 7587457

Results

solutioncategoryanswerverdict avg seconds cavg seconds py
p0088_s0 dev 30 correct 0.000001 (63) 0.000006 (63)
p0088_s0 main 7587457 correct 0.001132 (63) 0.059535 (63)

Files

Tags & blogs