Open Source · 7 Languages · 900+ Problems

Project Euler Solutions

Complete solutions in C++, Python & Java — with step-by-step mathematical explanations

All Problems

Problem 207: Integer Partition Equations

View on Project Euler

Project Euler Problem 207 Solution

EulerSolve provides an optimized solution for Project Euler Problem 207, Integer Partition Equations, with C++, Python, Java, and a step-by-step mathematical explanation.

Problem Summary The equation \(4^t = 2^t + k\) defines the partitions considered in this problem. If we set \(m = 2^t\), then \(4^t = m^2\), so every valid partition must satisfy $m^2 = m + k,\qquad k = m(m-1).$ Because the partition is required to be integral, \(m = 2^t\) must itself be an integer. Conversely, every integer \(m \ge 2\) gives one valid partition by taking \(t = \log_2 m\). Thus the problem is really about the sequence \(k_m = m(m-1)\) for \(m=2,3,4,\dots\). A partition is called perfect when \(t\) is an integer. In the new parameterization that means exactly that \(m\) is a power of two. For a bound \(K\), the quantity \(P(K)\) is the proportion of partitions with \(k \le K\) that are perfect. The goal is to find the smallest \(K\) for which \(P(K) < \frac{1}{12345}\). Mathematical Approach The key simplification is that the equation can be indexed by the integer \(m=2^t\). Once that is done, the only special values are the powers of two, so the whole problem becomes a discrete counting argument. Reparameterizing the equation Start from $4^t = 2^t + k.$ With \(m = 2^t\), we get \(4^t = (2^t)^2 = m^2\), so $m^2 = m + k,$ and therefore $k = m^2 - m = m(m-1).$ This already explains why the implementations never work with real exponents directly. They only need the integer parameter \(m\), because the exponent can always be recovered as \(t = \log_2 m\)....

Detailed mathematical approach

Problem Summary

The equation \(4^t = 2^t + k\) defines the partitions considered in this problem. If we set \(m = 2^t\), then \(4^t = m^2\), so every valid partition must satisfy

$m^2 = m + k,\qquad k = m(m-1).$

Because the partition is required to be integral, \(m = 2^t\) must itself be an integer. Conversely, every integer \(m \ge 2\) gives one valid partition by taking \(t = \log_2 m\). Thus the problem is really about the sequence \(k_m = m(m-1)\) for \(m=2,3,4,\dots\).

A partition is called perfect when \(t\) is an integer. In the new parameterization that means exactly that \(m\) is a power of two. For a bound \(K\), the quantity \(P(K)\) is the proportion of partitions with \(k \le K\) that are perfect. The goal is to find the smallest \(K\) for which \(P(K) < \frac{1}{12345}\).

Mathematical Approach

The key simplification is that the equation can be indexed by the integer \(m=2^t\). Once that is done, the only special values are the powers of two, so the whole problem becomes a discrete counting argument.

Reparameterizing the equation

Start from

$4^t = 2^t + k.$

With \(m = 2^t\), we get \(4^t = (2^t)^2 = m^2\), so

$m^2 = m + k,$

and therefore

$k = m^2 - m = m(m-1).$

This already explains why the implementations never work with real exponents directly. They only need the integer parameter \(m\), because the exponent can always be recovered as \(t = \log_2 m\).

Why each partition corresponds to one integer \(m\)

If \(m\) is an integer at least \(2\), then \(t=\log_2 m\) is a real number and

$4^t = (2^t)^2 = m^2,\qquad 2^t = m,$

so the equation becomes

$m^2 = m + m(m-1).$

This produces a valid integer partition with \(k=m(m-1)\). The mapping is one-to-one, and the associated \(k\)-values are strictly increasing because

$k_{m+1} - k_m = (m+1)m - m(m-1) = 2m > 0.$

That monotonicity is crucial: once we know the first \(m\) at which the target ratio falls below the threshold, the corresponding \(k=m(m-1)\) is automatically the smallest valid answer.

Identifying the perfect partitions

A partition is perfect exactly when \(t\) is an integer. Since \(m=2^t\), this happens exactly when \(m\) is a power of two:

$m \in \{2,4,8,16,\dots\}.$

So among the integers \(2 \le m \le M\), the number of perfect partitions is simply the number of powers of two not exceeding \(M\):

$C(M)=\left\lfloor \log_2 M \right\rfloor.$

There is no deeper recurrence hiding here. The perfect cases appear at very sparse, completely explicit positions.

Turning the bound on \(k\) into a bound on \(m\)

For a fixed upper bound \(K\), define

$M(K)=\max\{m \ge 2 : m(m-1)\le K\}.$

Because \(m \mapsto m(m-1)\) is strictly increasing, the partitions counted by \(P(K)\) are exactly those with

$2 \le m \le M(K).$

Hence the total number of partitions up to \(K\) is

$M(K)-1,$

and the number of perfect ones is

$\left\lfloor \log_2 M(K) \right\rfloor.$

Therefore

$P(K)=\frac{\left\lfloor \log_2 M(K) \right\rfloor}{M(K)-1}.$

The ratio does not change between consecutive values \(k_m\) and \(k_{m+1}\), so it is enough to test the prefixes indexed by \(M\).

The first-crossing inequality

Let the target threshold be \(\frac{1}{d}\). We want the smallest \(K\) such that

$P(K) < \frac{1}{d}.$

Writing everything in terms of \(M=M(K)\) gives

$\frac{\left\lfloor \log_2 M \right\rfloor}{M-1} < \frac{1}{d}$

which is equivalent to the integer comparison

$d \left\lfloor \log_2 M \right\rfloor < M-1.$

So the mathematical problem reduces to this:

Find the smallest integer \(M \ge 2\) such that

$d \left\lfloor \log_2 M \right\rfloor < M-1,$

and then return

$K = M(M-1).$

This is exactly the condition tested by the implementations.

Worked examples

The checkpoint threshold \(\frac{1}{3}\) is a useful example. At \(M=10\) the perfect values are \(2,4,8\), so

$P = \frac{3}{9} = \frac{1}{3},$

which does not satisfy the strict inequality. At \(M=11\) the perfect count is still \(3\), but now

$P = \frac{3}{10} < \frac{1}{3},$

so the first corresponding bound on \(k\) is

$K = 11 \cdot 10 = 110.$

The second checkpoint \(\frac{1}{10}\) behaves the same way. At \(M=51\),

$P = \frac{5}{50} = \frac{1}{10},$

so the threshold has not yet been crossed. At \(M=52\),

$P = \frac{5}{51} < \frac{1}{10},$

and the corresponding value is

$K = 52 \cdot 51 = 2652.$

These examples show why the code scans \(M\) in order and checks the inequality after updating the perfect-count state.

How the Code Works

The C++, Python, and Java implementations all follow the same discrete process. They never evaluate logarithms, never search over real \(t\), and never build a list of partitions explicitly.

Maintaining the prefix state

The scan runs through \(m=2,3,4,\dots\). Along the way the implementation keeps three pieces of information:

\(m\), the current integer parameter; the number of perfect partitions already seen; and the next power of two at which that perfect count must increase. After processing a given \(m\), the invariant is that the stored perfect count equals the number of powers of two in the interval \([2,m]\).

Updating perfect partitions without floating point

Whenever the current \(m\) reaches the next power of two, the perfect counter is incremented and the next threshold is doubled. This exactly mirrors the formula

$C(M)=\left\lfloor \log_2 M \right\rfloor,$

but it avoids repeated logarithm calls and avoids any dependence on floating-point rounding.

Testing the ratio and returning the answer

At each step, if \(c\) denotes the current number of perfect partitions seen so far, the implementation checks

$c \cdot d < m-1.$

As soon as it becomes true, the algorithm returns

$m(m-1).$

That returned value is correct because the scan visits the integers \(m\) in increasing order and \(m(m-1)\) is strictly increasing. The first successful \(m\) therefore yields the smallest possible bound on \(k\).

Implementation notes across the three languages

The shared algorithm is identical in all three languages. The C++ implementation also allows a configurable denominator and checks the sample thresholds \(\frac{1}{3}\) and \(\frac{1}{10}\) before the main computation. Python uses arbitrary-precision integers automatically. The C++ version widens the comparison product explicitly, and the Java version stays within 64-bit arithmetic for the default problem range.

Complexity Analysis

If \(M_\star\) is the first integer satisfying

$d \left\lfloor \log_2 M_\star \right\rfloor < M_\star-1,$

then the running time is \(O(M_\star)\), because each loop iteration performs only a constant amount of work. The memory usage is \(O(1)\): the algorithm stores just a few integers and no growing tables.

For this specific problem the search is efficient because the crossing occurs roughly when \(M\) is on the order of \(d \log d\), so a simple linear scan is entirely adequate. The main mathematical gain is not an advanced data structure but the correct reparameterization of the equation.

Footnotes and References

  1. Problem page: Project Euler 207
  2. Power of two: Wikipedia - Power of two
  3. Binary logarithm: Wikipedia - Binary logarithm
  4. Floor function: Wikipedia - Floor and ceiling functions

Mathematical approach · C++ solution · Python solution · Java solution

Previous: Problem 206 · All Project Euler solutions · Next: Problem 208

Need help with a problem? Ask me! 💡
e
✦ Euler GLM 5.2
Hi! I'm Euler. Ask me anything about Project Euler problems, math concepts, or solution approaches! 🧮