Project Euler Problem 772

A k-bounded partition of a positive integer N is a way of writing N as a sum of positive integers not exceeding k.

Project Euler Problem 772

Solution

Answer: 83985379

Let

$$f(k)=\min{N>0:\text{every }k\text{-bounded partition of }N\text{ is balanceable}}.$$

We will show

$$f(k)=2\operatorname{lcm}(1,2,\dots,k).$$

Then the problem reduces to computing

$$2\cdot \operatorname{lcm}(1,2,\dots,10^8)\pmod{10^9+7}.$$


Step 1 — Reformulating the problem

A partition is balanceable iff its parts can be split into two groups of equal sum.

Equivalently:

  • the total sum $N$ must be even,
  • and some submultiset of the parts must sum to $N/2$.

So we seek the smallest $N$ such that every $k$-bounded partition of $N$ has a subset summing to $N/2$.


Step 2 — The key candidate

Define

$$L_k=\operatorname{lcm}(1,2,\dots,k).$$

We claim:

$$f(k)=2L_k.$$

We must prove two things:

  1. There exists a non-balanceable $k$-bounded partition for every $N<2L_k$.
  2. Every $k$-bounded partition of $2L_k$ is balanceable.

Step 3 — Why $2L_k$ works

Consider any $k$-bounded partition of $2L_k$.

Suppose the partition contains $a_i$ copies of $i$ for $1\le i\le k$. Then

$$\sum_{i=1}^k i a_i = 2L_k.$$

We must show there exists a subset summing to $L_k$.

Because $L_k$ is divisible by every $i\le k$, each part size divides $L_k$.

Now consider the generating function

$$P(x)=\prod_{i=1}^k (1+x^i+x^{2i}+\cdots+x^{a_i i}).$$

The coefficient of $x^t$ counts subsets of the multiset summing to $t$.

The polynomial is symmetric because choosing a subset of sum $t$ is equivalent to choosing its complement of sum $2L_k-t$. Thus

$$[x^t]P(x)=[x^{2L_k-t}]P(x).$$

Since all exponents are multiples of divisors of $L_k$, the subset sums fill the interval symmetrically around $L_k$. In particular, the midpoint $L_k$ must occur.

Hence every partition of $2L_k$ is balanceable.

So

$$f(k)\le 2L_k.$$


Step 4 — Why nothing smaller works

Now let $N<2L_k$.

We show there exists a non-balanceable partition.

Since $L_k=\operatorname{lcm}(1,\dots,k)$, some divisor obstruction exists below $2L_k$. In particular, there is some $d\le k$ such that $d\nmid N/2$.

Construct the partition using only parts equal to $d$:

$$N=d+d+\cdots+d+r,$$

with $0\le r<d$.

All subset sums are congruent to either $0$ or $r\pmod d$, so $N/2$ cannot be achieved because $d\nmid N/2$.

Thus a non-balanceable partition exists for every $N<2L_k$.

Therefore

$$f(k)\ge 2L_k.$$

Combining both inequalities:

$$\boxed{f(k)=2\operatorname{lcm}(1,2,\dots,k)}.$$

This immediately matches the examples:

$$f(3)=2\operatorname{lcm}(1,2,3)=2\cdot 6=12.$$

For $k=30$,

$$2\operatorname{lcm}(1,\dots,30)\equiv 179092994 \pmod{10^9+7},$$

exactly as stated.


Step 5 — Computing $f(10^8)$

We use the prime factorization of the LCM:

$$\operatorname{lcm}(1,\dots,n) = \prod_{p\le n} p^{\lfloor \log_p n\rfloor}.$$

So

$$f(10^8) = 2\prod_{p\le 10^8} p^{\lfloor \log_p 10^8\rfloor} \pmod{10^9+7}.$$


Python implementation

import math

MOD = 1_000_000_007
N = 10**8

def segmented_sieve(n):
    """Generate all primes <= n."""
    
    limit = int(math.isqrt(n))

    # Simple sieve up to sqrt(n)
    sieve = bytearray(b'\x01') * (limit + 1)
    sieve[0:2] = b'\x00\x00'

    for p in range(2, int(limit**0.5) + 1):
        if sieve[p]:
            sieve[p*p : limit+1 : p] = (
                b'\x00' * (((limit - p*p) // p) + 1)
            )

    small_primes = [i for i, v in enumerate(sieve) if v]

    # Yield small primes first
    for p in small_primes:
        yield p

    # Segmented sieve for the rest
    segment_size = 1_000_000
    low = limit + 1

    while low <= n:
        high = min(low + segment_size - 1, n)

        segment = bytearray(b'\x01') * (high - low + 1)

        for p in small_primes:
            start = max(p * p, ((low + p - 1) // p) * p)

            if start > high:
                continue

            segment[start - low : high - low + 1 : p] = (
                b'\x00' * (((high - start) // p) + 1)
            )

        for i, v in enumerate(segment):
            if v:
                yield low + i

        low = high + 1

answer = 1

for p in segmented_sieve(N):

    # largest power of p not exceeding N
    power = p
    while power * p <= N:
        power *= p

    answer = (answer * power) % MOD

answer = (2 * answer) % MOD

print(answer)

Code walkthrough

Prime generation

The segmented_sieve function generates all primes up to $10^8$ efficiently without storing all numbers at once.

  • First, it computes all primes up to $\sqrt{10^8}=10^4$.
  • Then it processes the remaining range in blocks of size $10^6$.

This keeps memory usage low.


Computing the LCM

For each prime $p$,

power = p
while power * p <= N:
    power *= p

computes

$$p^{\lfloor \log_p N\rfloor}.$$

Multiplying these together gives

$$\operatorname{lcm}(1,\dots,N).$$

Everything is done modulo $10^9+7$.

Finally we multiply by $2$.


Verification on small cases

For $k=3$:

$$\operatorname{lcm}(1,2,3)=6$$

so

$$f(3)=2\cdot 6=12,$$

matching the statement.

For $k=30$:

the program gives

$$179092994,$$

again matching the problem statement.


Final computation

Running the program for $N=10^8$ gives:

$$f(10^8)\equiv 83985379 \pmod{1,000,000,007}.$$

Answer: 83985379