Problem 492: Exploding Sequence

View on Project Euler

Project Euler Problem 492 Solution

EulerSolve provides an optimized solution for Project Euler Problem 492, Exploding Sequence, with C++, Python, Java, and a step-by-step mathematical explanation.

Problem Summary The task is to evaluate $$B(x,y,n)=\sum_{\substack{p\ \text{prime}\\ x\le p\le x+y}} \left(a_n \bmod p\right),$$ for the specific target \(B(10^9,10^7,10^{15})\), where the sequence is defined by $$a_1=1,\qquad a_{t+1}=6a_t^2+10a_t+3.$$ The recurrence is explosive: every step squares the previous value, so computing \(a_n\) directly is hopeless when \(n=10^{15}\). The solution therefore avoids building the integer \(a_n\) itself and instead works modulo each prime in the interval. Mathematical Approach The central idea is to turn the quadratic recurrence into a Lucas-sequence problem whose enormous index can be reduced modulo a small group order. Step 1: Affine Change of Variables Introduce $$b_t=6a_t+5.$$ Then the recurrence becomes $$b_{t+1}=6a_{t+1}+5=6(6a_t^2+10a_t+3)+5=(6a_t+5)^2-2=b_t^2-2,$$ with initial value $$b_1=6\cdot 1+5=11.$$ This is the key simplification. Instead of a quadratic polynomial with three terms, we now have repeated application of the much cleaner map \(u\mapsto u^2-2\)....

Detailed mathematical approach

Problem Summary

The task is to evaluate

$$B(x,y,n)=\sum_{\substack{p\ \text{prime}\\ x\le p\le x+y}} \left(a_n \bmod p\right),$$

for the specific target \(B(10^9,10^7,10^{15})\), where the sequence is defined by

$$a_1=1,\qquad a_{t+1}=6a_t^2+10a_t+3.$$

The recurrence is explosive: every step squares the previous value, so computing \(a_n\) directly is hopeless when \(n=10^{15}\). The solution therefore avoids building the integer \(a_n\) itself and instead works modulo each prime in the interval.

Mathematical Approach

The central idea is to turn the quadratic recurrence into a Lucas-sequence problem whose enormous index can be reduced modulo a small group order.

Step 1: Affine Change of Variables

Introduce

$$b_t=6a_t+5.$$

Then the recurrence becomes

$$b_{t+1}=6a_{t+1}+5=6(6a_t^2+10a_t+3)+5=(6a_t+5)^2-2=b_t^2-2,$$

with initial value

$$b_1=6\cdot 1+5=11.$$

This is the key simplification. Instead of a quadratic polynomial with three terms, we now have repeated application of the much cleaner map \(u\mapsto u^2-2\).

Step 2: Recognize the Lucas Structure

Let

$$\alpha,\beta=\frac{11\pm\sqrt{117}}{2},\qquad \alpha+\beta=11,\qquad \alpha\beta=1.$$

Define the Lucas \(V\)-sequence with parameters \(P=11\) and \(Q=1\) by

$$V_m=\alpha^m+\beta^m.$$

Then

$$V_0=2,\qquad V_1=11,\qquad V_{m+1}=11V_m-V_{m-1}.$$

Because \(\alpha\beta=1\), we also have the doubling identity

$$V_{2m}=\alpha^{2m}+\beta^{2m}=(\alpha^m+\beta^m)^2-2(\alpha\beta)^m=V_m^2-2.$$

Since \(b_1=V_1\) and both sequences obey the same doubling rule, induction gives

$$b_t=V_{2^{t-1}}.$$

Therefore the original problem is equivalent to evaluating

$$a_n \bmod p \quad \text{from} \quad V_{2^{n-1}} \bmod p.$$

Step 3: Reduce the Giant Index

For the primes relevant to the target sum, \(p>13\), so the discriminant

$$\Delta=11^2-4=117$$

is nonzero modulo \(p\), and \(6\) is invertible modulo \(p\).

If \(117\) is a quadratic residue modulo \(p\), then \(\sqrt{117}\in\mathbb{F}_p\), so \(\alpha,\beta\in\mathbb{F}_p^\times\). Since \(\beta=\alpha^{-1}\), the value

$$V_k=\alpha^k+\alpha^{-k}$$

depends only on \(k\bmod(p-1)\).

If \(117\) is a quadratic nonresidue modulo \(p\), then \(\alpha\) and \(\beta\) live in \(\mathbb{F}_{p^2}\), and the Frobenius map swaps them:

$$\alpha^p=\beta=\alpha^{-1}.$$

Hence

$$\alpha^{p+1}=1,$$

so \(V_k\) depends only on \(k\bmod(p+1)\).

Thus the enormous index

$$k=2^{n-1}$$

is reduced by

$$k\equiv 2^{n-1}\pmod{p-1}\quad\text{if }\left(\frac{117}{p}\right)=1,$$

$$k\equiv 2^{n-1}\pmod{p+1}\quad\text{if }\left(\frac{117}{p}\right)=-1.$$

This turns an index of size roughly \(2^{10^{15}}\) into one smaller than \(p+1\).

Step 4: Fast Doubling for the Lucas Term

After the index has been reduced, the Lucas value is computed with standard doubling identities:

$$V_{2m}=V_m^2-2,$$

$$V_{2m+1}=V_mV_{m+1}-11,$$

$$V_{2m+2}=V_{m+1}^2-2.$$

Processing the bits of \(k\) from top to bottom keeps a pair of consecutive Lucas values and reaches \(V_k \bmod p\) in \(O(\log k)\) arithmetic steps.

Once \(b_n \equiv V_k \pmod p\) is known, we recover the original sequence via

$$a_n\equiv (b_n-5)\cdot 6^{-1}\pmod p.$$

Step 5: Sum Over the Prime Interval

The interval \([x,x+y]\) is handled with a segmented sieve. First, all base primes up to \(\sqrt{x+y}\) are generated. Then their multiples are marked inside the target interval, leaving exactly the primes that contribute to the sum.

For each prime \(p\) in the segment, the algorithm performs three modular tasks:

$$\text{determine whether }117\text{ is a residue mod }p,$$

$$\text{compute }2^{n-1}\text{ modulo }p-1\text{ or }p+1,$$

$$\text{evaluate }V_k\text{ and convert it back to }a_n\bmod p.$$

Adding those residues yields the required value of \(B(x,y,n)\).

Worked Example

A small example shows the reduction clearly. Take \(n=4\) and the interval \([5,7]\), so the contributing primes are \(5\) and \(7\).

For \(p=5\), \(117\equiv 2\pmod 5\), which is a nonresidue, so the index is reduced modulo \(p+1=6\):

$$k\equiv 2^{4-1}=8\equiv 2\pmod 6.$$

Then

$$V_2=11^2-2=119\equiv 4\pmod 5,$$

so

$$a_4\equiv (4-5)\cdot 6^{-1}\equiv (-1)\cdot 1\equiv 4\pmod 5.$$

For \(p=7\), \(117\equiv 5\pmod 7\), also a nonresidue, so the index is reduced modulo \(p+1=8\):

$$k\equiv 8\equiv 0\pmod 8.$$

Therefore

$$V_0=2,\qquad a_4\equiv (2-5)\cdot 6^{-1}\equiv (-3)\cdot 6\equiv 3\pmod 7.$$

Hence

$$B(5,2,4)=4+3=7.$$

How the Code Works

The C++, Python, and Java implementations follow the same pipeline. They first build a simple sieve up to \(\sqrt{x+y}\), then use those base primes to mark a segmented interval \([x,x+y]\). Every unmarked entry is a prime that contributes one term to the final sum.

For each such prime, the implementation computes a Legendre-style residue test for \(117\), chooses \(p-1\) or \(p+1\) as the period modulus, and evaluates \(2^{n-1}\) modulo that period with fast modular exponentiation. It then performs Lucas fast doubling on a pair of consecutive values until it reaches the reduced index \(k\), converts the result back from \(b_n\) to \(a_n\), and adds the residue to the running total.

No large integer value of \(a_n\) is ever constructed. Only modular values are propagated, which is why the method stays fast even when \(n=10^{15}\).

Complexity Analysis

Let \(H=x+y\), and let \(\pi(x,y)\) denote the number of primes in \([x,x+y]\). Building the base sieve up to \(\sqrt{H}\) costs \(O(\sqrt{H}\log\log H)\) time and \(O(\sqrt{H})\) memory. Marking the segment costs \(O(y\log\log H)\) time and \(O(y)\) memory.

For each prime in the segment, the modular residue test, the reduction of \(2^{n-1}\), and the Lucas doubling stage together require \(O(\log n+\log p)\) arithmetic operations. The overall running time is therefore

$$O\!\left(\sqrt{H}\log\log H+y\log\log H+\pi(x,y)(\log n+\log H)\right),$$

with memory usage

$$O(\sqrt{H}+y).$$

Footnotes and References

  1. Project Euler Problem 492
  2. Wikipedia - Lucas sequence
  3. Wikipedia - Finite field
  4. cp-algorithms - Fibonacci numbers and fast doubling
  5. cp-algorithms - Sieve of Eratosthenes

Problem 492 source code

C++

#include <cmath>
#include <cstdint>
#include <iostream>
#include <string>
#include <utility>
#include <vector>

namespace {

using u64 = std::uint64_t;
using u128 = __uint128_t;

struct Options {
    u64 x = 1'000'000'000ULL;
    u64 y = 10'000'000ULL;
    u64 n = 1'000'000'000'000'000ULL;
    bool run_checkpoints = true;
};

bool parse_u64_after_prefix(const std::string& arg, const std::string& prefix, u64& out) {
    if (arg.rfind(prefix, 0U) != 0U) {
        return false;
    }
    const std::string tail = arg.substr(prefix.size());
    if (tail.empty()) {
        return false;
    }
    try {
        out = static_cast<u64>(std::stoull(tail));
    } catch (...) {
        return false;
    }
    return true;
}

bool parse_arguments(int argc, char** argv, Options& options) {
    for (int i = 1; i < argc; ++i) {
        const std::string arg(argv[i]);
        if (arg == "--skip-checkpoints") {
            options.run_checkpoints = false;
            continue;
        }
        if (parse_u64_after_prefix(arg, "--x=", options.x)) {
            continue;
        }
        if (parse_u64_after_prefix(arg, "--y=", options.y)) {
            continue;
        }
        if (parse_u64_after_prefix(arg, "--n=", options.n)) {
            continue;
        }
        std::cerr << "Unknown argument: " << arg << '\n';
        return false;
    }
    return true;
}

u64 mod_pow(u64 base, u64 exp, const u64 mod) {
    if (mod == 1ULL) {
        return 0ULL;
    }
    u64 out = 1ULL % mod;
    base %= mod;
    while (exp > 0ULL) {
        if (exp & 1ULL) {
            out = static_cast<u64>((static_cast<u128>(out) * base) % mod);
        }
        base = static_cast<u64>((static_cast<u128>(base) * base) % mod);
        exp >>= 1ULL;
    }
    return out;
}

u64 mod_inv_prime(const u64 a, const u64 p) { return mod_pow(a, p - 2ULL, p); }

u64 add_mod(const u64 a, const u64 b, const u64 p) {
    const u64 c = a + b;
    return (c >= p || c < a) ? (c % p) : c;
}

u64 sub_mod(const u64 a, const u64 b, const u64 p) {
    return (a >= b) ? (a - b) : (a + p - b);
}

u64 mul_mod(const u64 a, const u64 b, const u64 p) {
    return static_cast<u64>((static_cast<u128>(a) * b) % p);
}

std::vector<int> simple_primes_up_to(const int limit) {
    std::vector<bool> is_prime(static_cast<std::size_t>(limit + 1), true);
    if (limit >= 0) {
        is_prime[0] = false;
    }
    if (limit >= 1) {
        is_prime[1] = false;
    }
    for (int p = 2; static_cast<int64_t>(p) * p <= limit; ++p) {
        if (!is_prime[static_cast<std::size_t>(p)]) {
            continue;
        }
        for (int x = p * p; x <= limit; x += p) {
            is_prime[static_cast<std::size_t>(x)] = false;
        }
    }
    std::vector<int> primes;
    for (int i = 2; i <= limit; ++i) {
        if (is_prime[static_cast<std::size_t>(i)]) {
            primes.push_back(i);
        }
    }
    return primes;
}

std::vector<u64> primes_in_segment(const u64 lo, const u64 hi) {
    const int root = static_cast<int>(std::sqrt(static_cast<long double>(hi))) + 1;
    const std::vector<int> base_primes = simple_primes_up_to(root);

    std::vector<bool> is_prime(static_cast<std::size_t>(hi - lo + 1ULL), true);
    for (const int p : base_primes) {
        u64 start = (lo + static_cast<u64>(p) - 1ULL) / static_cast<u64>(p) * static_cast<u64>(p);
        if (start < static_cast<u64>(p) * static_cast<u64>(p)) {
            start = static_cast<u64>(p) * static_cast<u64>(p);
        }
        for (u64 x = start; x <= hi; x += static_cast<u64>(p)) {
            is_prime[static_cast<std::size_t>(x - lo)] = false;
        }
    }
    if (lo == 0ULL) {
        if (!is_prime.empty()) {
            is_prime[0] = false;
        }
        if (is_prime.size() > 1U) {
            is_prime[1] = false;
        }
    } else if (lo == 1ULL) {
        is_prime[0] = false;
    }

    std::vector<u64> out;
    out.reserve(static_cast<std::size_t>((hi - lo + 1ULL) / std::log(static_cast<long double>(hi))));
    for (u64 x = lo; x <= hi; ++x) {
        if (is_prime[static_cast<std::size_t>(x - lo)]) {
            out.push_back(x);
        }
    }
    return out;
}

u64 lucas_V_mod(const u64 k, const u64 p, const u64 P = 11ULL) {
    // Fast doubling on V_n(P,1):
    // V_{2m}   = V_m^2 - 2
    // V_{2m+1} = V_m V_{m+1} - P
    // V_{2m+2} = V_{m+1}^2 - 2
    if (k == 0ULL) {
        return 2ULL % p;
    }

    u64 vm = 2ULL % p;   // V_0
    u64 vmp1 = P % p;    // V_1

    int msb = 63 - __builtin_clzll(k);
    for (int bit = msb; bit >= 0; --bit) {
        const u64 v2m = sub_mod(mul_mod(vm, vm, p), 2ULL % p, p);
        const u64 v2m1 = sub_mod(mul_mod(vm, vmp1, p), P % p, p);
        const u64 v2m2 = sub_mod(mul_mod(vmp1, vmp1, p), 2ULL % p, p);

        if (((k >> bit) & 1ULL) == 0ULL) {
            vm = v2m;
            vmp1 = v2m1;
        } else {
            vm = v2m1;
            vmp1 = v2m2;
        }
    }
    return vm;
}

u64 a_n_mod_p(const u64 n, const u64 p) {
    if (n == 1ULL) {
        return 1ULL % p;
    }

    // b_n = 6 a_n + 5, b_{n+1}=b_n^2-2, b_1=11.
    // b_n = V_{2^{n-1}}(11,1) in F_p / F_{p^2}.
    // The index can be reduced modulo p-1 if 117 is a residue, otherwise p+1.
    const u64 leg = mod_pow(117ULL % p, (p - 1ULL) / 2ULL, p);
    const bool residue = (leg == 1ULL);
    const u64 order_mod = residue ? (p - 1ULL) : (p + 1ULL);

    const u64 idx = mod_pow(2ULL, n - 1ULL, order_mod);
    const u64 b = lucas_V_mod(idx, p, 11ULL);

    const u64 inv6 = mod_inv_prime(6ULL, p);
    const u64 a = mul_mod(sub_mod(b, 5ULL % p, p), inv6, p);
    return a;
}

u64 B(const u64 x, const u64 y, const u64 n) {
    const u64 lo = x;
    const u64 hi = x + y;
    const std::vector<u64> primes = primes_in_segment(lo, hi);

    u64 total = 0ULL;
    for (const u64 p : primes) {
        total += a_n_mod_p(n, p);
    }
    return total;
}

bool run_checkpoints() {
    if (a_n_mod_p(6ULL, 1'000'000'007ULL) != 203'064'689ULL) {
        std::cerr << "Checkpoint failed: a_6 mod 1e9+7\n";
        return false;
    }
    if (a_n_mod_p(100ULL, 1'000'000'007ULL) != 456'482'974ULL) {
        std::cerr << "Checkpoint failed: a_100 mod 1e9+7\n";
        return false;
    }
    if (B(1'000'000'000ULL, 1'000ULL, 1'000ULL) != 23'674'718'882ULL) {
        std::cerr << "Checkpoint failed: B(1e9,1e3,1e3)\n";
        return false;
    }
    if (B(1'000'000'000ULL, 1'000ULL, 1'000'000'000'000'000ULL) != 20'731'563'854ULL) {
        std::cerr << "Checkpoint failed: B(1e9,1e3,1e15)\n";
        return false;
    }
    return true;
}

}  // namespace

int main(int argc, char** argv) {
    Options options;
    if (!parse_arguments(argc, argv, options)) {
        return 1;
    }
    if (options.run_checkpoints && !run_checkpoints()) {
        return 1;
    }

    std::cout << B(options.x, options.y, options.n) << '\n';
    return 0;
}

Python

import math

def simple_primes_up_to(limit):
    is_prime = [True] * (limit + 1)
    if limit >= 0: is_prime[0] = False
    if limit >= 1: is_prime[1] = False
    p = 2
    while p * p <= limit:
        if is_prime[p]:
            for x in range(p * p, limit + 1, p):
                is_prime[x] = False
        p += 1
    return [i for i in range(2, limit + 1) if is_prime[i]]

def primes_in_segment(lo, hi):
    root = int(math.sqrt(hi)) + 1
    base_primes = simple_primes_up_to(root)
    is_prime = [True] * (hi - lo + 1)
    for p in base_primes:
        start = (lo + p - 1) // p * p
        if start < p * p:
            start = p * p
        for x in range(start, hi + 1, p):
            is_prime[x - lo] = False
            
    if lo == 0:
        if len(is_prime) > 0: is_prime[0] = False
        if len(is_prime) > 1: is_prime[1] = False
    elif lo == 1:
        is_prime[0] = False
        
    out = []
    for x in range(lo, hi + 1):
        if is_prime[x - lo]:
            out.append(x)
    return out

def lucas_V_mod(k, p, P=11):
    if k == 0: return 2 % p
    vm = 2 % p
    vmp1 = P % p
    msb = k.bit_length() - 1
    for bit in range(msb, -1, -1):
        v2m = (vm * vm - 2) % p
        v2m1 = (vm * vmp1 - P) % p
        v2m2 = (vmp1 * vmp1 - 2) % p
        if ((k >> bit) & 1) == 0:
            vm, vmp1 = v2m, v2m1
        else:
            vm, vmp1 = v2m1, v2m2
    return vm

def a_n_mod_p(n, p):
    if n == 1: return 1 % p
    leg = pow(117 % p, (p - 1) // 2, p)
    order_mod = (p - 1) if leg == 1 else (p + 1)
    idx = pow(2, n - 1, order_mod)
    b = lucas_V_mod(idx, p, 11)
    inv6 = pow(6, p - 2, p)
    a = ((b - 5) % p * inv6) % p
    return a

def B(x, y, n):
    lo = x
    hi = x + y
    primes = primes_in_segment(lo, hi)
    total = 0
    for p in primes:
        total += a_n_mod_p(n, p)
    return total

def solve():
    x = 1000000000
    y = 10000000
    n = 1000000000000000
    return str(B(x, y, n))

if __name__ == '__main__':
    print(solve())

Java

import java.util.ArrayList;
import java.util.List;

public class Euler492 {

    private static long modPow(long base, long exp, long mod) {
        if (mod == 1L)
            return 0L;
        long out = 1L % mod;
        base %= mod;
        while (exp > 0L) {
            if ((exp & 1L) != 0L) {
                out = mulMod(out, base, mod);
            }
            base = mulMod(base, base, mod);
            exp >>= 1L;
        }
        return out;
    }

    private static long modInvPrime(long a, long p) {
        return modPow(a, p - 2L, p);
    }

    private static long subMod(long a, long b, long p) {
        return (a >= b) ? (a - b) : (a + p - b);
    }

    private static long mulMod(long a, long b, long mod) {
        return ((a % mod) * (b % mod)) % mod;
    }

    private static List<Integer> simplePrimesUpTo(int limit) {
        boolean[] isPrime = new boolean[limit + 1];
        for (int i = 2; i <= limit; i++)
            isPrime[i] = true;

        for (long p = 2; p * p <= limit; ++p) {
            if (!isPrime[(int) p])
                continue;
            for (long x = p * p; x <= limit; x += p) {
                isPrime[(int) x] = false;
            }
        }

        List<Integer> primes = new ArrayList<>();
        for (int i = 2; i <= limit; ++i) {
            if (isPrime[i])
                primes.add(i);
        }
        return primes;
    }

    private static List<Long> primesInSegment(long lo, long hi) {
        int root = (int) Math.sqrt(hi) + 1;
        List<Integer> basePrimes = simplePrimesUpTo(root);

        int len = (int) (hi - lo + 1);
        boolean[] isPrime = new boolean[len];
        for (int i = 0; i < len; i++)
            isPrime[i] = true;

        for (int p : basePrimes) {
            long start = (lo + p - 1) / p * p;
            if (start < (long) p * p)
                start = (long) p * p;
            for (long x = start; x <= hi; x += p) {
                isPrime[(int) (x - lo)] = false;
            }
        }

        if (lo == 0) {
            if (len > 0)
                isPrime[0] = false;
            if (len > 1)
                isPrime[1] = false;
        } else if (lo == 1) {
            if (len > 0)
                isPrime[0] = false;
        }

        List<Long> out = new ArrayList<>();
        for (long x = lo; x <= hi; ++x) {
            if (isPrime[(int) (x - lo)])
                out.add(x);
        }
        return out;
    }

    private static long lucasVMod(long k, long p, long P) {
        if (k == 0L)
            return 2L % p;

        long vm = 2L % p;
        long vmp1 = P % p;

        int msb = 63 - Long.numberOfLeadingZeros(k);
        for (int bit = msb; bit >= 0; --bit) {
            long v2m = subMod(mulMod(vm, vm, p), 2L % p, p);
            long v2m1 = subMod(mulMod(vm, vmp1, p), P % p, p);
            long v2m2 = subMod(mulMod(vmp1, vmp1, p), 2L % p, p);

            if (((k >> bit) & 1L) == 0L) {
                vm = v2m;
                vmp1 = v2m1;
            } else {
                vm = v2m1;
                vmp1 = v2m2;
            }
        }
        return vm;
    }

    private static long aNModP(long n, long p) {
        if (n == 1L)
            return 1L % p;

        long leg = modPow(117L % p, (p - 1L) / 2L, p);
        boolean residue = (leg == 1L);
        long orderMod = residue ? (p - 1L) : (p + 1L);

        long idx = modPow(2L, n - 1L, orderMod);
        long b = lucasVMod(idx, p, 11L);

        long inv6 = modInvPrime(6L, p);
        long a = mulMod(subMod(b, 5L % p, p), inv6, p);
        return a;
    }

    private static long B(long x, long y, long n) {
        long lo = x;
        long hi = x + y;
        List<Long> primes = primesInSegment(lo, hi);

        long total = 0L;
        for (long p : primes) {
            total += aNModP(n, p);
        }
        return total;
    }

    public static void main(String[] args) {
        long x = 1000000000L;
        long y = 10000000L;
        long n = 1000000000000000L;
        System.out.println(B(x, y, n));
    }
}