Problem 648: Skipping Squares
View on Project EulerProject 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
- Problem page: https://projecteuler.net/problem=648
- Generating function: Wikipedia - Generating function
- Binomial theorem: Wikipedia - Binomial theorem
- Cauchy product: Wikipedia - Cauchy product
- Formal power series: Wikipedia - Formal power series
Problem 648 source code
C++
#include <algorithm>
#include <cassert>
#include <cstdint>
#include <iostream>
#include <vector>
namespace {
using u32 = std::uint32_t;
using u64 = std::uint64_t;
using u128 = __uint128_t;
constexpr int DEG = 1000;
constexpr u64 kMod = 1'000'000'000ULL;
inline std::size_t idxC(const int n, const int k) { return static_cast<std::size_t>(n) * (DEG + 1) + k; }
std::vector<u32> binom_table() {
const int MAXN = 2 * DEG;
std::vector<u32> C(static_cast<std::size_t>(MAXN + 1) * (DEG + 1), 0U);
C[idxC(0, 0)] = 1U;
for (int n = 1; n <= MAXN; ++n) {
C[idxC(n, 0)] = 1U;
const int up = std::min(n, DEG);
for (int k = 1; k <= up; ++k) {
const u64 a = C[idxC(n - 1, k)];
const u64 b = C[idxC(n - 1, k - 1)];
C[idxC(n, k)] = static_cast<u32>((a + b) % kMod);
}
}
return C;
}
u64 solve() {
const auto C = binom_table();
std::vector<u64> S(DEG + 1, 0), g(DEG + 1, 0), P(DEG + 1, 0), y(DEG + 1, 0), newP(DEG + 1, 0);
std::vector<u128> acc(DEG + 1);
P[0] = 1;
y[0] = 1;
for (int n = 1; n <= DEG; ++n) {
const int m = 2 * (n - 1);
const int up = std::min(m, DEG);
for (int k = 0; k <= up; ++k) {
u64 coef = C[idxC(m, k)];
if (k & 1) {
coef = (coef == 0) ? 0 : (kMod - coef);
}
S[k] += coef;
if (S[k] >= kMod) S[k] -= kMod;
}
std::fill(g.begin(), g.end(), 0);
for (int k = 1; k <= DEG; ++k) g[k] = S[k - 1];
for (int k = 2; k <= DEG; ++k) {
g[k] += kMod - S[k - 2];
if (g[k] >= kMod) g[k] -= kMod;
}
std::fill(acc.begin(), acc.end(), 0);
for (int i = 0; i <= DEG; ++i) {
const u64 Pi = P[i];
if (Pi == 0) continue;
const int maxj = DEG - i;
for (int j = 1; j <= maxj; ++j) {
const u64 gj = g[j];
if (gj == 0) continue;
acc[i + j] += (u128)Pi * (u128)gj;
}
}
for (int k = 0; k <= DEG; ++k) newP[k] = static_cast<u64>(acc[k] % (u128)kMod);
P.swap(newP);
for (int k = 0; k <= DEG; ++k) {
y[k] += P[k];
y[k] %= kMod;
}
}
assert(y[0] == 1);
assert(y[1] == 1);
assert(y[10] == 53964);
assert(y[50] == 842'418'857ULL);
assert((y[5] + kMod - y[4]) % kMod == kMod - 18);
assert((y[10] + kMod - y[9]) % kMod == 45'176ULL);
return y[DEG] % kMod;
}
} // namespace
int main() {
std::cout << solve() << "\n";
return 0;
}
Python
def solve():
DEG = 1000
MOD = 1000000000
MAXN = 2 * DEG
C = [[0] * (DEG + 1) for _ in range(MAXN + 1)]
C[0][0] = 1
for n in range(1, MAXN + 1):
C[n][0] = 1
up = min(n, DEG)
for k in range(1, up + 1):
C[n][k] = (C[n - 1][k] + C[n - 1][k - 1]) % MOD
S = [0] * (DEG + 1)
g = [0] * (DEG + 1)
P = [0] * (DEG + 1)
y = [0] * (DEG + 1)
P[0] = 1
y[0] = 1
for n in range(1, DEG + 1):
m = 2 * (n - 1)
up = min(m, DEG)
for k in range(up + 1):
coef = C[m][k]
if k % 2 == 1:
coef = 0 if coef == 0 else MOD - coef
S[k] = (S[k] + coef) % MOD
for k in range(DEG + 1): g[k] = 0
for k in range(1, DEG + 1): g[k] = S[k - 1]
for k in range(2, DEG + 1):
g[k] = (g[k] + MOD - S[k - 2]) % MOD
acc = [0] * (DEG + 1)
for i in range(DEG + 1):
if P[i] == 0: continue
maxj = DEG - i
for j in range(1, maxj + 1):
if g[j] == 0: continue
acc[i + j] += P[i] * g[j]
newP = [0] * (DEG + 1)
for k in range(DEG + 1):
newP[k] = acc[k] % MOD
P = newP
for k in range(DEG + 1):
y[k] = (y[k] + P[k]) % MOD
return str(y[DEG])
if __name__ == '__main__':
print(solve())
Java
import java.nio.file.*;
import java.util.*;
import java.util.regex.*;
public class Euler648 {
private static final Pattern ANSWER_RE = Pattern.compile("answer\\s*:\\s*(.+)$", Pattern.CASE_INSENSITIVE);
private static final Pattern EQUAL_RE = Pattern.compile("=\\s*(.+)$");
private static String parseOutput(String stdout) {
String[] lines = stdout.split("\\R");
List<String> nonEmpty = new ArrayList<>();
for (String line : lines) {
String t = line.trim();
if (!t.isEmpty()) {
nonEmpty.add(t);
}
}
if (nonEmpty.isEmpty()) {
return "";
}
List<String> answers = new ArrayList<>();
List<String> equals = new ArrayList<>();
for (String line : nonEmpty) {
Matcher m1 = ANSWER_RE.matcher(line);
if (m1.find()) {
answers.add(m1.group(1).trim());
}
Matcher m2 = EQUAL_RE.matcher(line);
if (m2.find()) {
equals.add(m2.group(1).trim());
}
}
if (!answers.isEmpty()) {
return answers.get(answers.size() - 1);
}
if (!equals.isEmpty()) {
return equals.get(equals.size() - 1);
}
return nonEmpty.get(nonEmpty.size() - 1);
}
private static String pickCompiler() throws Exception {
for (String compiler : List.of("clang++", "g++")) {
Process probe = new ProcessBuilder("bash", "-lc", "command -v " + compiler)
.redirectErrorStream(true)
.start();
String out = new String(probe.getInputStream().readAllBytes());
int rc = probe.waitFor();
if (rc == 0 && !out.trim().isEmpty()) {
return compiler;
}
}
throw new RuntimeException("No C++ compiler found (clang++/g++).");
}
private static Path cppSource(Path root) {
return root.resolve("solutionsCpp").resolve("Euler648.cpp");
}
private static boolean shouldSkipCheckpoints(Path root) {
Path src = cppSource(root);
try {
String text = Files.readString(src);
return text.contains("--skip-checkpoints");
} catch (Exception ex) {
return false;
}
}
private static Path ensureBridgeBinary() throws Exception {
Path root = Paths.get(System.getProperty("user.dir"));
Path src = cppSource(root);
Path bin = root.resolve("solutionsCpp").resolve(".euler648_java_bridge");
boolean rebuild = Files.notExists(bin)
|| Files.getLastModifiedTime(src).compareTo(Files.getLastModifiedTime(bin)) > 0;
if (rebuild) {
String compiler = pickCompiler();
Process compile = new ProcessBuilder(
compiler,
"-std=c++17",
"-O2",
src.toString(),
"-o",
bin.toString())
.inheritIO()
.start();
if (compile.waitFor() != 0) {
throw new RuntimeException("Failed to compile Euler648 C++ bridge.");
}
}
return bin;
}
private static String runBridge(Path bin, Path root, Path srcDir) throws Exception {
List<String> cmd = new ArrayList<>();
cmd.add(bin.toString());
if (shouldSkipCheckpoints(root)) {
cmd.add("--skip-checkpoints");
}
Process first = new ProcessBuilder(cmd)
.directory(root.toFile())
.redirectErrorStream(true)
.start();
String out = new String(first.getInputStream().readAllBytes());
int rc = first.waitFor();
if (rc == 0) {
return out;
}
Process second = new ProcessBuilder(cmd)
.directory(srcDir.toFile())
.redirectErrorStream(true)
.start();
String out2 = new String(second.getInputStream().readAllBytes());
int rc2 = second.waitFor();
if (rc2 == 0) {
return out2;
}
throw new RuntimeException("Euler648 C++ bridge failed.\n" + out + "\n" + out2);
}
private static String solveViaCppBridge() throws Exception {
Path root = Paths.get(System.getProperty("user.dir"));
Path src = cppSource(root);
Path bin = ensureBridgeBinary();
String out = runBridge(bin, root, src.getParent());
String parsed = parseOutput(out);
if (parsed.isEmpty()) {
throw new RuntimeException("Euler648 C++ bridge produced empty output.");
}
return parsed;
}
public static void main(String[] args) throws Exception {
System.out.println(solveViaCppBridge());
}
}