Problem 921: Golden Recurrence
View on Project EulerProject Euler Problem 921 Solution
EulerSolve provides an optimized solution for Project Euler Problem 921, Golden Recurrence, with C++, Python, Java, and a step-by-step mathematical explanation.
Problem Summary Let \(p=398874989\) and \(G=p^2-1\). The computation is carried out in the two-dimensional algebra $$R=\mathbb{F}_p[u]/(u^2-5),\qquad \beta=u-2,$$ where \(u\) plays the role of \(\sqrt5\). For the Fibonacci numbers $$F_1=F_2=1,\qquad F_n=F_{n-1}+F_{n-2},$$ define the reduced exponent $$e_n\equiv 5^{F_n}\pmod G.$$ If $$\beta^{e_n}=a_n+b_nu,$$ then the \(n\)-th contribution is $$s_n=b_n^5+(-a_n)^5\pmod p.$$ The goal is to evaluate $$S=\sum_{n=2}^{1{,}618{,}034}s_n\pmod p.$$ Directly forming the Fibonacci exponents is hopeless. The successful approach is to keep only a short modular exponent recurrence and to evaluate powers of \(\beta\) inside the fixed algebra \(R\). Mathematical Approach The three implementations all exploit the same structure: the golden-looking base \(\sqrt5-2\), the multiplicative behavior of \(5^{F_n}\), and the fact that every relevant power can be reconstructed from a 64-entry binary table. The split quadratic algebra and the golden element The algebra \(R\) is represented in the basis \(1,u\) with \(u^2=5\). Multiplication is therefore $$\left(a+bu\right)\left(c+du\right)=(ac+5bd)+(ad+bc)u\pmod p.$$ This is exactly the rule used by the implementations. It keeps every power of \(\beta\) in the form \(a+bu\), so the final summand is obtained by reading off two coefficients. The adjective "golden" is not cosmetic....
Detailed mathematical approach
Problem Summary
Let \(p=398874989\) and \(G=p^2-1\). The computation is carried out in the two-dimensional algebra
$$R=\mathbb{F}_p[u]/(u^2-5),\qquad \beta=u-2,$$
where \(u\) plays the role of \(\sqrt5\). For the Fibonacci numbers
$$F_1=F_2=1,\qquad F_n=F_{n-1}+F_{n-2},$$
define the reduced exponent
$$e_n\equiv 5^{F_n}\pmod G.$$
If
$$\beta^{e_n}=a_n+b_nu,$$
then the \(n\)-th contribution is
$$s_n=b_n^5+(-a_n)^5\pmod p.$$
The goal is to evaluate
$$S=\sum_{n=2}^{1{,}618{,}034}s_n\pmod p.$$
Directly forming the Fibonacci exponents is hopeless. The successful approach is to keep only a short modular exponent recurrence and to evaluate powers of \(\beta\) inside the fixed algebra \(R\).
Mathematical Approach
The three implementations all exploit the same structure: the golden-looking base \(\sqrt5-2\), the multiplicative behavior of \(5^{F_n}\), and the fact that every relevant power can be reconstructed from a 64-entry binary table.
The split quadratic algebra and the golden element
The algebra \(R\) is represented in the basis \(1,u\) with \(u^2=5\). Multiplication is therefore
$$\left(a+bu\right)\left(c+du\right)=(ac+5bd)+(ad+bc)u\pmod p.$$
This is exactly the rule used by the implementations. It keeps every power of \(\beta\) in the form \(a+bu\), so the final summand is obtained by reading off two coefficients.
The adjective "golden" is not cosmetic. If
$$\phi=\frac{1+\sqrt5}{2},$$
then \(\phi^3=2+\sqrt5\), hence
$$\beta=\sqrt5-2=\frac{1}{2+\sqrt5}=\phi^{-3}.$$
Also,
$$\left(u-2\right)\left(u+2\right)=u^2-4=1,$$
so \(\beta\) is a unit of the algebra and every positive power is well-defined.
For this specific prime, \(p\equiv 4\pmod 5\). Since \(5\equiv 1\pmod 4\), quadratic reciprocity gives \(\left(\frac{5}{p}\right)=\left(\frac{p}{5}\right)=\left(\frac{4}{5}\right)=1\), so \(u^2-5\) splits over \(\mathbb{F}_p\). In other words, \(R\) is a split quadratic algebra, isomorphic to \(\mathbb{F}_p\times\mathbb{F}_p\). The code does not need that isomorphism explicitly; the coefficient basis \(1,u\) is already enough.
Reducing gigantic exponents to a short modular recurrence
The Fibonacci recurrence immediately gives
$$5^{F_n}=5^{F_{n-1}+F_{n-2}}=5^{F_{n-1}}5^{F_{n-2}}.$$
Because \(R^\times\cong \mathbb{F}_p^\times\times\mathbb{F}_p^\times\), every unit has multiplicative period dividing \(p-1\). So any multiple of \(p-1\) is a safe modulus for exponents. The implementations use the larger quantity
$$G=p^2-1=(p-1)(p+1),$$
which is not minimal but is still perfectly valid because \(p-1\mid G\).
That lets us replace the astronomical integer \(5^{F_n}\) by the residue
$$e_n\equiv 5^{F_n}\pmod G.$$
The residues satisfy the compact recurrence
$$e_1=e_2=5,\qquad e_n\equiv e_{n-1}e_{n-2}\pmod G\quad(n\ge 3).$$
This is the decisive simplification. Instead of carrying Fibonacci numbers or giant exponentials, the algorithm advances the entire exponent side with one modular multiplication per step.
Conjugation and the norm give a permanent invariant
The algebra has an involution \(u\mapsto -u\), so
$$\overline{a+bu}=a-bu.$$
The associated norm is
$$N(a+bu)=(a+bu)(a-bu)=a^2-5b^2\pmod p.$$
For the base element \(\beta=u-2\),
$$N(\beta)=(u-2)(-u-2)=4-u^2=-1.$$
Every \(e_n\) is odd: the sequence starts from \(5,5\), and modulo the even number \(G\) the product of odd residues remains odd. Therefore
$$N\!\left(\beta^{e_n}\right)=N(\beta)^{e_n}=(-1)^{e_n}=-1.$$
So if
$$\beta^{e_n}=a_n+b_nu,$$
then the coefficients always satisfy
$$a_n^2-5b_n^2\equiv -1\pmod p.$$
This Pell-type congruence is not the final answer, but it explains the shape of every coefficient pair and provides a clean mathematical consistency check.
Worked example: the first accumulated terms
Starting from \(\beta=u-2\), repeated multiplication gives
$$\beta^1=-2+u,$$
$$\beta^2=9-4u,$$
$$\beta^3=-38+17u,$$
$$\beta^4=161-72u,$$
$$\beta^5=-682+305u.$$
The tiny checkpoint at exponent \(1\) is
$$s(1)=1^5+2^5=33,$$
because \(\beta^1=-2+u\). The actual sum begins at \(n=2\), where \(e_2=5\), so the first accumulated term is
$$s_2=305^5+682^5\equiv 257933744\pmod p.$$
The next reduced exponent is
$$e_3\equiv e_2e_1\equiv 5\cdot 5\equiv 25\pmod G,$$
and evaluating \(\beta^{25}\) in the same basis gives
$$s_3\equiv 26500067\pmod p.$$
Those two values are exactly the small checkpoints embedded in the implementations. After that, the same recurrence-and-powering pattern continues all the way to \(n=1{,}618{,}034\).
How the Code Works
Representing algebra elements
Each algebra element is stored as a pair of residues \((a,b)\) representing \(a+bu\). Multiplication uses the formula above, so every operation stays in two coordinates modulo \(p\). The quantity \((-a)^5\) is computed modulo \(p\) as the fifth power of the additive inverse of the first coordinate.
Precomputing binary powers of \(\beta\)
The C++, Python, and Java implementations precompute
$$\beta^{2^0},\beta^{2^1},\dots,\beta^{2^{63}}$$
by repeated squaring. That table is sufficient because the chosen exponent modulus satisfies \(0\le e_n<G<2^{64}\). Any required power \(\beta^{e_n}\) is then reconstructed from the binary expansion of \(e_n\).
Streaming the exponent recurrence and the final sum
The implementations never materialize a Fibonacci number. They keep only the two latest exponent residues, both initially equal to \(5\). The answer is initialized with the \(n=2\) term, and then for each \(n\ge 3\) they compute
$$e_n\equiv e_{n-1}e_{n-2}\pmod G,$$
rebuild \(\beta^{e_n}\) from the binary table, extract \(a_n\) and \(b_n\), evaluate \(b_n^5+(-a_n)^5\pmod p\), and add it to the running sum. The three language versions differ only in low-level modular-multiplication details; the mathematical pipeline is identical.
Complexity Analysis
Let \(M=1{,}618{,}034\). Precomputing the binary table costs \(64\) algebra multiplications. Each subsequent term uses one modular multiplication for the exponent recurrence and at most \(64\) algebra multiplications to reconstruct \(\beta^{e_n}\) from its bits. Therefore the running time is
$$O(M\log G),$$
and since \(\log G<64\), this is effectively linear in the number of required indices.
The memory usage is \(O(1)\): a constant-size power table, two current exponents, a running total, and a few temporary algebra elements.
Footnotes and References
- Project Euler problem page: https://projecteuler.net/problem=921
- Fibonacci number: Wikipedia - Fibonacci number
- Golden ratio: Wikipedia - Golden ratio
- Modular arithmetic: Wikipedia - Modular arithmetic
- Quotient ring: Wikipedia - Quotient ring
- Field norm: Wikipedia - Field norm
- Quadratic reciprocity: Wikipedia - Quadratic reciprocity
- Exponentiation by squaring: Wikipedia - Exponentiation by squaring
Problem 921 source code
C++
#include <array>
#include <cassert>
#include <cstdint>
#include <iostream>
namespace {
using u64 = std::uint64_t;
using u128 = unsigned __int128;
constexpr u64 kMod = 398874989ULL;
constexpr u64 kGroup = kMod * kMod - 1ULL;
struct Elem {
u64 a;
u64 b;
};
u64 add_mod(u64 x, u64 y, u64 mod) {
x += y;
if (x >= mod) {
x -= mod;
}
return x;
}
u64 mul_mod(u64 x, u64 y, u64 mod) {
return static_cast<u64>((static_cast<u128>(x) * y) % mod);
}
Elem mul_elem(const Elem& x, const Elem& y) {
const u64 aa = mul_mod(x.a, y.a, kMod);
const u64 bb = mul_mod(x.b, y.b, kMod);
const u64 real = add_mod(aa, (5ULL * bb) % kMod, kMod);
const u64 imag = add_mod(mul_mod(x.a, y.b, kMod), mul_mod(x.b, y.a, kMod), kMod);
return {real, imag};
}
std::array<Elem, 64> precompute_powers(const Elem& base) {
std::array<Elem, 64> powers{};
powers[0] = base;
for (int i = 1; i < 64; ++i) {
powers[i] = mul_elem(powers[i - 1], powers[i - 1]);
}
return powers;
}
Elem pow_from_powers(u64 exp, const std::array<Elem, 64>& powers) {
Elem result{1, 0};
while (exp != 0) {
const int bit = __builtin_ctzll(exp);
result = mul_elem(result, powers[bit]);
exp &= (exp - 1);
}
return result;
}
Elem pow_elem(Elem base, u64 exp) {
Elem result{1, 0};
while (exp != 0) {
if (exp & 1ULL) {
result = mul_elem(result, base);
}
exp >>= 1U;
if (exp != 0) {
base = mul_elem(base, base);
}
}
return result;
}
u64 pow_mod(u64 base, u64 exp, u64 mod) {
u64 result = 1 % mod;
u64 value = base % mod;
while (exp != 0) {
if (exp & 1ULL) {
result = mul_mod(result, value, mod);
}
exp >>= 1U;
if (exp != 0) {
value = mul_mod(value, value, mod);
}
}
return result;
}
u64 pow5_mod(u64 x) {
const u64 x2 = mul_mod(x, x, kMod);
const u64 x4 = mul_mod(x2, x2, kMod);
return mul_mod(x4, x, kMod);
}
u64 s_from_exp(u64 exp, const std::array<Elem, 64>& base_powers) {
const Elem value = pow_from_powers(exp, base_powers);
const u64 p = value.b;
const u64 q = (value.a == 0 ? 0 : kMod - value.a);
return add_mod(pow5_mod(p), pow5_mod(q), kMod);
}
u64 solve() {
constexpr int kM = 1'618'034;
const Elem base{(kMod + kMod - 2) % kMod, 1}; // -2 + sqrt(5)
const auto base_powers = precompute_powers(base);
u64 exp_prev2 = 5; // 5^{F_1}
u64 exp_prev1 = 5; // 5^{F_2}
u64 answer = s_from_exp(exp_prev1, base_powers); // i=2
for (int i = 3; i <= kM; ++i) {
const u64 current = mul_mod(exp_prev1, exp_prev2, kGroup);
answer = add_mod(answer, s_from_exp(current, base_powers), kMod);
exp_prev2 = exp_prev1;
exp_prev1 = current;
}
return answer;
}
void validate() {
const Elem base{(kMod + kMod - 2) % kMod, 1};
const auto base_powers = precompute_powers(base);
assert(s_from_exp(1, base_powers) == 33);
assert(s_from_exp(5, base_powers) == 257933744);
assert(s_from_exp(25, base_powers) == 26500067);
Elem stepped = base;
u64 exp = 1;
for (int n = 0; n <= 20; ++n) {
const Elem by_exp = pow_from_powers(exp, base_powers);
assert(by_exp.a == stepped.a && by_exp.b == stepped.b);
stepped = pow_elem(stepped, 5);
exp = mul_mod(exp, 5, kGroup);
}
u64 fib_prev2 = 1;
u64 fib_prev1 = 1;
u64 fexp_prev2 = 5;
u64 fexp_prev1 = 5;
for (int i = 3; i <= 25; ++i) {
const u64 fib_cur = fib_prev1 + fib_prev2;
const u64 fexp_cur = mul_mod(fexp_prev1, fexp_prev2, kGroup);
assert(fexp_cur == pow_mod(5, fib_cur, kGroup));
fib_prev2 = fib_prev1;
fib_prev1 = fib_cur;
fexp_prev2 = fexp_prev1;
fexp_prev1 = fexp_cur;
}
}
} // namespace
int main() {
validate();
std::cout << solve() << '\n';
return 0;
}
Python
def solve():
MOD = 398874989
GROUP = MOD * MOD - 1
kM = 1618034
def add_mod(x, y): return (x + y) % MOD
def mul_mod(x, y): return x * y % MOD
def mul_elem(x, y):
aa = x[0]*y[0]%MOD; bb = x[1]*y[1]%MOD
real = (aa + 5*bb) % MOD
imag = (x[0]*y[1] + x[1]*y[0]) % MOD
return (real, imag)
def pow_elem(base, exp):
r = (1, 0)
while exp > 0:
if exp & 1: r = mul_elem(r, base)
exp >>= 1
if exp > 0: base = mul_elem(base, base)
return r
def pow5(x):
x2 = x*x%MOD; x4 = x2*x2%MOD; return x4*x%MOD
base = ((MOD + MOD - 2) % MOD, 1)
# Precompute powers of base for fast exponentiation
powers = [base]
for _ in range(63): powers.append(mul_elem(powers[-1], powers[-1]))
def pow_base(exp):
r = (1, 0)
while exp:
b = (exp & -exp).bit_length() - 1
r = mul_elem(r, powers[b]); exp &= exp - 1
return r
def s_from_exp(exp):
v = pow_base(exp)
p = v[1]; q = (MOD - v[0]) % MOD
return (pow5(p) + pow5(q)) % MOD
ep2 = 5; ep1 = 5 # 5^{F_1}, 5^{F_2}
answer = s_from_exp(ep1)
for i in range(3, kM + 1):
cur = ep1 * ep2 % GROUP
answer = (answer + s_from_exp(cur)) % MOD
ep2, ep1 = ep1, cur
return str(answer)
if __name__ == '__main__':
print(solve())
Java
public class Euler921 {
static final long MOD = 398874989L;
static final long GROUP = MOD * MOD - 1L;
static class Elem {
long a, b;
Elem(long a, long b) {
this.a = a;
this.b = b;
}
}
static long addMod(long x, long y, long mod) {
long res = x + y;
if (res >= mod)
res -= mod;
return res;
}
static long mulMod(long x, long y, long mod) {
long res = 0;
long a = x;
long b = y;
while (b > 0) {
if ((b & 1L) != 0) {
res = addMod(res, a, mod);
}
a = addMod(a, a, mod);
b >>= 1L;
}
return res;
}
static Elem mulElem(Elem x, Elem y) {
long aa = mulMod(x.a, y.a, MOD);
long bb = mulMod(x.b, y.b, MOD);
long real = addMod(aa, mulMod(5L, bb, MOD), MOD);
long imag = addMod(mulMod(x.a, y.b, MOD), mulMod(x.b, y.a, MOD), MOD);
return new Elem(real, imag);
}
static Elem[] precomputePowers(Elem base) {
Elem[] powers = new Elem[64];
powers[0] = base;
for (int i = 1; i < 64; ++i) {
powers[i] = mulElem(powers[i - 1], powers[i - 1]);
}
return powers;
}
static Elem powFromPowers(long exp, Elem[] powers) {
Elem result = new Elem(1, 0);
int bit = 0;
while (exp != 0) {
if ((exp & 1L) != 0) {
result = mulElem(result, powers[bit]);
}
exp >>= 1L;
bit++;
}
return result;
}
static long pow5Mod(long x) {
long x2 = mulMod(x, x, MOD);
long x4 = mulMod(x2, x2, MOD);
return mulMod(x4, x, MOD);
}
static long sFromExp(long exp, Elem[] powers) {
Elem value = powFromPowers(exp, powers);
long p = value.b;
long q = value.a == 0 ? 0 : MOD - value.a;
return addMod(pow5Mod(p), pow5Mod(q), MOD);
}
public static String solve() {
int kM = 1618034;
Elem base = new Elem((MOD + MOD - 2) % MOD, 1);
Elem[] basePowers = precomputePowers(base);
long expPrev2 = 5;
long expPrev1 = 5;
long answer = sFromExp(expPrev1, basePowers);
for (int i = 3; i <= kM; ++i) {
long current = mulMod(expPrev1, expPrev2, GROUP);
answer = addMod(answer, sFromExp(current, basePowers), MOD);
expPrev2 = expPrev1;
expPrev1 = current;
}
return Long.toString(answer);
}
public static void main(String[] args) {
System.out.println(solve());
}
}