Open Source · 7 Languages · 1007 Problems + Bonus

Project Euler Solutions

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

All Problems

Problem 648: Skipping Squares

View on Project Euler

Project Euler Problem 648 Solution

EulerSolve provides an optimized solution for Project Euler Problem 648, Skipping Squares, with C++, Python, Java, and a step-by-step mathematical explanation.

Problem Summary The implementations show that Problem 648 can be reformulated as a coefficient-extraction problem for truncated formal power series. With \(D=1000\) and modulus \(M=10^9\), the objective is to compute the coefficient of \(x^D\) in a cumulative series built from stage polynomials. Because only terms up to degree \(D\) can affect the final answer, every polynomial is truncated after \(x^{1000}\). Mathematical Approach Write \([x^k]F(x)\) for the coefficient of \(x^k\) in a series \(F(x)\). The computation is organized around one polynomial per stage and a running product of those stage factors. Step 1: Build the Base Polynomial from Even Powers of \(1-x\) At stage \(n\), the implementation accumulates the coefficients of the finite sum $$\mathcal{A}_n(x)=\sum_{j=0}^{n-1}(1-x)^{2j}.$$ Using the binomial theorem, each summand contributes $$[x^k](1-x)^{2j}=(-1)^k\binom{2j}{k}.$$ Therefore the coefficient of \(x^k\) in \(\mathcal{A}_n(x)\) is $$[x^k]\mathcal{A}_n(x)=\sum_{j=0}^{n-1}(-1)^k\binom{2j}{k}.$$ This is why the code first builds a truncated Pascal triangle up to row \(2D\): every stage only needs binomial coefficients coming from even exponents \(0,2,4,\dots,2D-2\), and only degrees \(k\le D\) matter....

Detailed mathematical approach

Problem Summary

The implementations show that Problem 648 can be reformulated as a coefficient-extraction problem for truncated formal power series. With \(D=1000\) and modulus \(M=10^9\), the objective is to compute the coefficient of \(x^D\) in a cumulative series built from stage polynomials. Because only terms up to degree \(D\) can affect the final answer, every polynomial is truncated after \(x^{1000}\).

Mathematical Approach

Write \([x^k]F(x)\) for the coefficient of \(x^k\) in a series \(F(x)\). The computation is organized around one polynomial per stage and a running product of those stage factors.

Step 1: Build the Base Polynomial from Even Powers of \(1-x\)

At stage \(n\), the implementation accumulates the coefficients of the finite sum

$$\mathcal{A}_n(x)=\sum_{j=0}^{n-1}(1-x)^{2j}.$$

Using the binomial theorem, each summand contributes

$$[x^k](1-x)^{2j}=(-1)^k\binom{2j}{k}.$$

Therefore the coefficient of \(x^k\) in \(\mathcal{A}_n(x)\) is

$$[x^k]\mathcal{A}_n(x)=\sum_{j=0}^{n-1}(-1)^k\binom{2j}{k}.$$

This is why the code first builds a truncated Pascal triangle up to row \(2D\): every stage only needs binomial coefficients coming from even exponents \(0,2,4,\dots,2D-2\), and only degrees \(k\le D\) matter.

Step 2: Turn that Base Polynomial into the Stage Factor

The next polynomial is obtained by multiplying \(\mathcal{A}_n(x)\) by \(x(1-x)\):

$$\mathcal{B}_n(x)=x(1-x)\mathcal{A}_n(x).$$

Coefficient-wise this means

$$[x^k]\mathcal{B}_n(x)=[x^{k-1}]\mathcal{A}_n(x)-[x^{k-2}]\mathcal{A}_n(x),$$

where missing coefficients are interpreted as \(0\). This shift-and-subtract rule is exactly what the implementations apply.

Because \(\mathcal{A}_n(x)\) is a geometric sum, there is also a closed form:

$$\mathcal{A}_n(x)=\frac{1-(1-x)^{2n}}{1-(1-x)^2}=\frac{1-(1-x)^{2n}}{2x-x^2}.$$

Hence

$$\mathcal{B}_n(x)=x(1-x)\mathcal{A}_n(x)=\frac{(1-x)\bigl(1-(1-x)^{2n}\bigr)}{2-x}.$$

This rational-looking form is only an algebraic identity. The actual computation never divides modulo \(10^9\); it works entirely with finite coefficient arrays, additions, subtractions, and convolutions.

Step 3: Chain the Stage Factors into the Target Series

Define the running products by

$$\mathcal{R}_0(x)=1,\qquad \mathcal{R}_n(x)=\mathcal{R}_{n-1}(x)\mathcal{B}_n(x)\quad (n\ge 1).$$

The cumulative series whose degree-\(D\) coefficient is required is

$$\mathcal{T}_D(x)=1+\sum_{n=1}^{D}\mathcal{R}_n(x).$$

So the answer is

$$\boxed{[x^D]\mathcal{T}_D(x)\bmod 10^9.}$$

Equivalently,

$$\mathcal{T}_D(x)=1+\sum_{n=1}^{D}\prod_{i=1}^{n}\left(x(1-x)\sum_{j=0}^{i-1}(1-x)^{2j}\right).$$

Each stage factor is divisible by \(x\), so \(\mathcal{R}_n(x)\) is divisible by \(x^n\). Therefore no term with \(n>D\) can contribute to \([x^D]\), which explains why the loop stops exactly at \(D=1000\).

Step 4: Extract Coefficients by Truncated Cauchy Convolution

If

$$\mathcal{R}_{n-1}(x)=\sum_{i=0}^{D} r_i x^i,\qquad \mathcal{B}_n(x)=\sum_{j=0}^{D} b_j x^j,$$

then the next product satisfies

$$[x^k]\mathcal{R}_n(x)=\sum_{i=0}^{k} r_i\,b_{k-i}\qquad (0\le k\le D).$$

This is the Cauchy product for series, truncated after degree \(D\). It is the dominant operation in the program: for each stage, every relevant coefficient of the current product is combined with every relevant coefficient of the new factor, and the result is reduced modulo \(10^9\).

Step 5: Worked Example with the First Two Stages

At \(n=1\),

$$\mathcal{A}_1(x)=1,\qquad \mathcal{B}_1(x)=x(1-x)=x-x^2.$$

So

$$\mathcal{R}_1(x)=x-x^2.$$

At \(n=2\),

$$\mathcal{A}_2(x)=1+(1-x)^2=2-2x+x^2,$$

and therefore

$$\mathcal{B}_2(x)=x(1-x)(2-2x+x^2)=2x-4x^2+3x^3-x^4.$$

Multiplying the first two stage factors gives

$$\mathcal{R}_2(x)=(x-x^2)(2x-4x^2+3x^3-x^4)=2x^2-6x^3+7x^4-4x^5+x^6.$$

So after two stages the cumulative series begins

$$1+\mathcal{R}_1(x)+\mathcal{R}_2(x)=1+x+x^2-6x^3+7x^4-4x^5+x^6+\cdots.$$

If we were only interested in degree \(4\), every later computation would keep only the truncation \(1+x+x^2-6x^3+7x^4\). The full problem performs this same process up to degree \(1000\).

How the Code Works

The C++, Python, and Java implementations all follow the same mathematical pipeline. First they precompute the needed binomial coefficients modulo \(10^9\) using Pascal's identity, keeping only columns \(0\) through \(D\). Next, stage \(n\) adds the coefficients of \((1-x)^{2(n-1)}\) into the running even-power sum, then applies the coefficient shift corresponding to multiplication by \(x(1-x)\) to obtain the new stage factor.

After that, the implementation multiplies the current product by the new stage factor using a truncated convolution, stores only degrees \(0\) through \(D\), and adds the new product into the running answer series. The C++ version uses a wider temporary accumulator before taking the modulus; the mathematical recurrence itself is the same across all three languages.

Complexity Analysis

Let \(D=1000\). Building the truncated binomial table up to row \(2D\) and column \(D\) costs \(O(D^2)\) time and \(O(D^2)\) memory. Updating the stage factor itself is only \(O(D)\) per stage, but the truncated convolution for the running product is \(O(D^2)\) per stage. Repeating that over \(D\) stages gives total time \(O(D^3)\). The memory usage is dominated by the binomial table, so the overall space complexity is \(O(D^2)\).

Footnotes and References

  1. Problem page: https://projecteuler.net/problem=648
  2. Generating function: Wikipedia - Generating function
  3. Binomial theorem: Wikipedia - Binomial theorem
  4. Cauchy product: Wikipedia - Cauchy product
  5. Formal power series: Wikipedia - Formal power series

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

Previous: Problem 647 · All Project Euler solutions · Next: Problem 649