All Euler problems
Project Euler

Primonacci

Let p_i denote the i -th prime number. Define a(i) = F(p_(10^14+i)), where F(n) is the n -th Fibonacci number with F(1) = F(2) = 1. Find sum_(i=1)^100000 a(i) (mod 1234567891011).

Source sync Apr 19, 2026
Problem #0304
Level Level 09
Solved By 2,560
Languages C++, Python
Answer 283988410192
Length 301 words
modular_arithmeticnumber_theorylinear_algebra

Problem Statement

This archive keeps the full statement, math, and original media on the page.

For any positive integer $n$ the function $\operatorname{next\_prime}(n)$ returns the smallest prime $p$ such that $p > n$.

The sequence $a(n)$ is defined by: $$ \begin{cases} a(1)=\operatorname{next\_prime}(10^{14}) \\ (n)=\operatorname{next\_prime}(a(n-1)) \text{, for } n > 1 \end{cases} $$

The Fibonacci sequence $f(n)$ is defined by:

$$\begin{cases} f(0) = 1 \\ f(1) = 1 \\ f(n) = f(n - 1) + f(n - 2) \text{, for } n > 1 \end{cases}$$

The sequence $b(n)$ is defined as $f(a(n))$.

Find $\displaystyle \sum b(n)$ for $1 \le n \le 100\,000$. Give your answer mod $1234567891011$.

Problem 304: Primonacci

Mathematical Foundation

Theorem 1 (Fibonacci matrix identity). For all n1n \ge 1,

(F(n+1)F(n)F(n)F(n1))=(1110)n.\begin{pmatrix} F(n+1) & F(n) \\ F(n) & F(n-1) \end{pmatrix} = \begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix}^n.

In particular, F(n)=M1,0nF(n) = M^n_{1,0} where M=(1110)M = \begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix}.

Proof. By induction on nn. Base case n=1n = 1: M1=(1110)M^1 = \begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix}, which gives F(2)=1F(2) = 1, F(1)=1F(1) = 1, F(0)=0F(0) = 0. Inductive step: if the identity holds for nn, then

Mn+1=MnM=(F(n+1)F(n)F(n)F(n1))(1110)=(F(n+1)+F(n)F(n+1)F(n)+F(n1)F(n))=(F(n+2)F(n+1)F(n+1)F(n)),M^{n+1} = M^n \cdot M = \begin{pmatrix} F(n+1) & F(n) \\ F(n) & F(n-1) \end{pmatrix} \begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix} = \begin{pmatrix} F(n+1)+F(n) & F(n+1) \\ F(n)+F(n-1) & F(n) \end{pmatrix} = \begin{pmatrix} F(n+2) & F(n+1) \\ F(n+1) & F(n) \end{pmatrix},

using the Fibonacci recurrence F(k+1)=F(k)+F(k1)F(k+1) = F(k) + F(k-1). \square

Theorem 2 (Matrix exponentiation by squaring). For any 2×22 \times 2 matrix MM and positive integer nn, MnmodmM^n \bmod m can be computed in O(logn)O(\log n) matrix multiplications modulo mm.

Proof. Write nn in binary as n=j2bjn = \sum_{j} 2^{b_j}. Then Mn=jM2bjM^n = \prod_j M^{2^{b_j}}. Each M2j+1=(M2j)2M^{2^{j+1}} = (M^{2^j})^2 is computed by one squaring. The total number of multiplications is at most 2log2n2\lfloor \log_2 n \rfloor. \square

Lemma 1 (Incremental Fibonacci computation). Given MpkmodmM^{p_k} \bmod m, we can compute Mpk+1modmM^{p_{k+1}} \bmod m using O(g)O(g) matrix multiplications, where g=pk+1pkg = p_{k+1} - p_k is the prime gap, via Mpk+1=MpkMgM^{p_{k+1}} = M^{p_k} \cdot M^g.

Proof. By associativity of matrix multiplication. Computing MgM^g for small gg requires O(logg)O(\log g) multiplications via repeated squaring, or O(g)O(g) via iterated multiplication by MM. Since gg is typically small (36\sim 36 near 3×10153 \times 10^{15} by the prime number theorem), this is efficient. \square

Theorem 3 (Prime location via PNT). The 101410^{14}-th prime is approximately 1014ln(1014)3.22×101510^{14} \ln(10^{14}) \approx 3.22 \times 10^{15}. A segmented sieve of Eratosthenes, combined with the Meissel—Lehmer prime counting algorithm, locates the exact primes p1014+1,,p1014+100000p_{10^{14}+1}, \ldots, p_{10^{14}+100000}.

Proof. By the prime number theorem, pnnlnnp_n \sim n \ln n. The Meissel—Lehmer algorithm computes π(x)\pi(x) in O(x2/3/lnx)O(x^{2/3}/\ln x) time, enabling binary search for the exact starting point. A segmented sieve of length LL then enumerates consecutive primes in O(L+x)O(L + \sqrt{x}) time. \square

Editorial

.100000, where p_k is the k-th prime and F(n) is the n-th Fibonacci number. Strategy: 1. Use sympy for prime counting and generation near 10^14-th prime. 2. Use matrix exponentiation for Fibonacci mod m. 3. Incremental computation via gap-based matrix multiplication. We use Meissel-Lehmer to compute pi(x_start) and adjust. We then extract the 100000 primes starting from the correct offset. Finally, compute F(p_1) mod m via matrix exponentiation.

Pseudocode

Locate primes p_{10^14+1} through p_{10^14+100000}
Use Meissel-Lehmer to compute pi(x_start) and adjust
Extract the 100000 primes starting from the correct offset
Compute F(p_1) mod m via matrix exponentiation
Incremental computation for subsequent primes

Complexity Analysis

  • Time:
    • Prime sieve: O(N+L)O(\sqrt{N} + L) where N3.22×1015N \approx 3.22 \times 10^{15} and LL is the sieve segment length (107\sim 10^7).
    • First Fibonacci: O(logN)O(52)O(\log N) \approx O(52) matrix multiplications.
    • Subsequent: O(100000loggˉ)O(100000 \cdot \log \bar{g}) where gˉ36\bar{g} \approx 36, so 5×105\sim 5 \times 10^5 multiplications.
    • Total: O(N+100000loggˉ)O(\sqrt{N} + 100000 \cdot \log \bar{g}).
  • Space: O(N)O(\sqrt{N}) for the base sieve, O(L)O(L) for the segment, O(1)O(1) for matrices.

Answer

283988410192\boxed{283988410192}

Code

Each problem page includes the exact C++ and Python source files from the local archive.

C++ project_euler/problem_304/solution.cpp
#include <bits/stdc++.h>
using namespace std;

/*
 * Problem 304: Primonacci
 *
 * Find sum of F(p_{10^14+i}) mod 1234567891011 for i=1..100000.
 * p_k is the k-th prime, F(n) is the n-th Fibonacci number.
 *
 * Steps:
 * 1. Use Lucy_Hedgehog to compute pi(x) and binary search for a point near
 *    the 10^14-th prime.
 * 2. Segmented sieve to collect 100000 consecutive primes from that point.
 * 3. Matrix exponentiation for Fibonacci with incremental gaps.
 */

typedef long long ll;
typedef __int128 lll;

const ll MOD = 1234567891011LL;

struct Mat { ll a[2][2]; };

Mat mat_mul(const Mat& A, const Mat& B, ll m) {
    Mat C;
    for (int i = 0; i < 2; i++)
        for (int j = 0; j < 2; j++) {
            lll s = 0;
            for (int k = 0; k < 2; k++)
                s += (lll)A.a[i][k] * B.a[k][j];
            C.a[i][j] = (ll)(s % m);
        }
    return C;
}

Mat mat_pow(Mat M, ll n, ll m) {
    Mat R = {{{1,0},{0,1}}};
    while (n > 0) {
        if (n & 1) R = mat_mul(R, M, m);
        M = mat_mul(M, M, m);
        n >>= 1;
    }
    return R;
}

vector<int> small_primes;

void gen_small_primes(int lim) {
    vector<bool> sieve(lim + 1, false);
    for (int i = 2; i <= lim; i++) {
        if (!sieve[i]) {
            small_primes.push_back(i);
            for (ll j = (ll)i * i; j <= lim; j += i)
                sieve[j] = true;
        }
    }
}

vector<ll> seg_sieve(ll lo, ll hi) {
    if (lo < 2) lo = 2;
    int sz = (int)(hi - lo + 1);
    vector<bool> is_composite(sz, false);
    for (int p : small_primes) {
        if ((ll)p * p > hi) break;
        ll start = max((ll)p * p, ((lo + p - 1) / p) * p);
        for (ll j = start; j <= hi; j += p)
            is_composite[(int)(j - lo)] = true;
    }
    vector<ll> result;
    for (int i = 0; i < sz; i++)
        if (!is_composite[i])
            result.push_back(lo + i);
    return result;
}

ll prime_count(ll n) {
    if (n < 2) return 0;
    ll sqrtn = (ll)sqrt((double)n);
    while ((sqrtn + 1) * (sqrtn + 1) <= n) sqrtn++;
    while (sqrtn * sqrtn > n) sqrtn--;

    int sz = (int)sqrtn;
    int* Ssmall = new int[sz + 2];
    ll* Slarge = new ll[sz + 2];

    Ssmall[0] = 0; Slarge[0] = 0;
    for (int i = 1; i <= sz; i++) {
        Ssmall[i] = i - 1;
        Slarge[i] = n / i - 1;
    }

    for (ll p = 2; p <= sqrtn; p++) {
        if (Ssmall[p] == Ssmall[p - 1]) continue;
        int pcnt = Ssmall[p - 1];
        ll p2 = p * p;
        ll lim = min((ll)sz, n / p2);
        for (ll i = 1; i <= lim; i++) {
            ll d = i * p;
            ll val = (d <= sz) ? Slarge[d] : Ssmall[n / d];
            Slarge[i] -= (val - pcnt);
        }
        for (ll i = sz; i >= p2; i--) {
            Ssmall[i] -= (Ssmall[i / p] - pcnt);
        }
    }

    ll result = Slarge[1];
    delete[] Ssmall;
    delete[] Slarge;
    return result;
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);

    ll target = 100000000000000LL; // 10^14

    // Need primes up to sqrt(4e15) ~ 63M
    gen_small_primes(64000000);

    // Binary search for x such that pi(x) is close to target.
    // p_{10^14} ~ 10^14 * (ln(10^14) + ln(ln(10^14))) ~ 10^14 * 35.7 ~ 3.57e15
    //
    // We do binary search, calling prime_count at the midpoint each time.
    // Each call takes ~2-3 minutes for x ~ 3.5e15. With ~30 bisection steps this is too slow.
    //
    // Instead: call prime_count ONCE at a good estimate, then use segmented sieve
    // to adjust by at most ~10^8 numbers.

    // Better estimate using Cipolla's asymptotic: p_n ~ n(ln n + ln ln n - 1 + (ln ln n - 2)/ln n)
    // For n = 10^14:
    // ln(10^14) = 32.2362
    // ln(ln(10^14)) = ln(32.2362) = 3.4730
    // p ~ 10^14 * (32.2362 + 3.4730 - 1 + (3.4730 - 2)/32.2362)
    //   = 10^14 * (34.7092 + 0.04569)
    //   = 10^14 * 34.7549
    //   = 3.47549e15

    // Let's try a single prime_count call at 3.4755e15
    ll x0 = 3475500000000000LL;

    fprintf(stderr, "Computing pi(%lld)...\n", x0);
    ll pi0 = prime_count(x0);
    fprintf(stderr, "pi(%lld) = %lld, target = %lld, diff = %lld\n",
            x0, pi0, target, target - pi0);

    // diff = target - pi0. Each prime gap ~ ln(x0) ~ 35.8
    // Adjust: approximately diff * 36 positions forward (or back if negative)
    ll diff = target - pi0;
    ll gap_est = 36;

    // If |diff| is small enough (< ~3M primes ~ 10^8 positions), we can sieve
    // Otherwise, do another prime_count call at x0 + diff*gap_est

    if (abs(diff) > 3000000LL) {
        ll x1 = x0 + diff * gap_est;
        fprintf(stderr, "Computing pi(%lld)...\n", x1);
        ll pi1 = prime_count(x1);
        fprintf(stderr, "pi(%lld) = %lld, diff = %lld\n", x1, pi1, target - pi1);
        x0 = x1;
        pi0 = pi1;
        diff = target - pi0;
    }

    if (abs(diff) > 3000000LL) {
        // One more iteration
        ll x1 = x0 + diff * gap_est;
        fprintf(stderr, "Computing pi(%lld)...\n", x1);
        ll pi1 = prime_count(x1);
        fprintf(stderr, "pi(%lld) = %lld, diff = %lld\n", x1, pi1, target - pi1);
        x0 = x1;
        pi0 = pi1;
        diff = target - pi0;
    }

    // Now |diff| should be small. Sieve forward/backward.
    ll sieve_start;
    ll primes_to_skip;

    if (diff >= 0) {
        sieve_start = x0 + 1;
        primes_to_skip = diff;
    } else {
        // Go back: |diff| primes * ~36 + safety margin
        sieve_start = x0 + diff * gap_est - 5000000;
        if (sieve_start < 2) sieve_start = 2;

        // Count primes in [sieve_start, x0] to find pi(sieve_start-1)
        ll count_in_range = 0;
        ll seg_sz = 10000000;
        for (ll lo = sieve_start; lo <= x0; lo += seg_sz) {
            ll hi = min(lo + seg_sz - 1, x0);
            auto ps = seg_sieve(lo, hi);
            count_in_range += ps.size();
        }
        // pi(sieve_start - 1) = pi0 - count_in_range
        primes_to_skip = target - (pi0 - count_in_range);
    }

    fprintf(stderr, "Sieve from %lld, skip %lld primes, collect 100000\n",
            sieve_start, primes_to_skip);

    // Collect 100000 primes
    vector<ll> target_primes;
    target_primes.reserve(100000);
    ll seg_size = 10000000;
    ll sieve_lo = sieve_start;

    while ((ll)target_primes.size() < 100000) {
        ll sieve_hi = sieve_lo + seg_size - 1;
        auto primes_seg = seg_sieve(sieve_lo, sieve_hi);

        for (ll p : primes_seg) {
            if (primes_to_skip > 0) {
                primes_to_skip--;
            } else {
                target_primes.push_back(p);
                if ((ll)target_primes.size() >= 100000) break;
            }
        }
        sieve_lo = sieve_hi + 1;
    }

    fprintf(stderr, "Collected %zu primes: [%lld, %lld]\n",
            target_primes.size(), target_primes.front(), target_primes.back());

    // Compute sum of F(p_i) mod MOD
    Mat M = {{{1,1},{1,0}}};
    Mat cur = mat_pow(M, target_primes[0] - 1, MOD);
    ll total = cur.a[0][0] % MOD;

    for (int i = 1; i < (int)target_primes.size(); i++) {
        ll g = target_primes[i] - target_primes[i-1];
        Mat Mg = mat_pow(M, g, MOD);
        cur = mat_mul(cur, Mg, MOD);
        total = (total + cur.a[0][0]) % MOD;
    }

    cout << total << endl;
    return 0;
}