Lab 02 — Binary Exponentiation and Matrix Exponentiation for Fibonacci
Goal
Master O(log b) exponentiation in two settings: integer fast power (a^b mod p) and matrix fast power (Fibonacci F(n) mod p for n up to 10^18). By the end, you can write integer fast power in <2 minutes and matrix Fibonacci in <10 minutes from blank.
Background
Binary exponentiation is the engine behind almost every “compute a^b for huge b” subroutine in CP and cryptography. The same divide-and-conquer pattern — a^b = (a^(b/2))^2 if b even, else a · a^(b-1) — generalizes from integers to any associative operation: matrix multiplication (linear recurrences), polynomial multiplication (signal processing), function composition (rotation by an angle, repeated f(f(f(...)))). Internalize the O(log b) skeleton once and you get all these for free.
Matrix exponentiation is the canonical extension. Fibonacci, defined F(n) = F(n-1) + F(n-2), has the matrix form [F(n+1), F(n)]^T = M · [F(n), F(n-1)]^T where M = [[1, 1], [1, 0]]. Therefore [F(n+1), F(n)]^T = M^n · [F(1), F(0)]^T = M^n · [1, 0]^T. Computing M^n takes O(log n) matrix multiplications, each O(2^3) = O(8) operations. Total: O(log n) for n = 10^18, about 60 multiplications.
Interview Context
Quant interviews use exactly this pair. “Compute 2^(10^18) mod (10^9 + 7)” is a 2-minute warm-up. “Compute F(10^18) mod (10^9 + 7)” is the follow-up that filters out candidates who only know the iterative O(N) Fibonacci. A candidate who reaches for matrix exponentiation reflexively when seeing n ≤ 10^18 and a linear recurrence is signalling 1900+ CF rating, which is a strong positive signal at HFT firms.
Problem Statement
Part 1. Implement power(a, b, m) returning a^b mod m for 0 ≤ a < m, 0 ≤ b ≤ 10^18, 1 ≤ m ≤ 10^9 + 7.
Part 2. Implement fib(n, p) returning F(n) mod p for 0 ≤ n ≤ 10^18, p = 10^9 + 7, where F(0) = 0, F(1) = 1, F(n) = F(n-1) + F(n-2).
LeetCode reference: LC 50 (Pow(x, n)) — Part 1 in real-number form. LC 1137 (N-th Tribonacci) — Part 2 with three-term recurrence (analogous matrix form).
Constraints
- Part 1:
bup to10^18, so naiveO(b)looping is impossible. - Part 2:
nup to10^18, so naiveO(n)Fibonacci is impossible. - Time limit: 2 seconds.
- Memory: O(1) for Part 1, O(1) for Part 2 (matrices are 2×2).
Clarifying Questions
- “Negative
bfor integer power?” — for modulara^b, undefined unlessgcd(a, m) = 1and you wanta^(-1)^|b|. For reala^b(LC 50), return1 / a^|b|and watch forINT_MINoverflow on-b. - “What’s
0^0?” — by convention1. - “Fibonacci indexing — is
F(1) = 1orF(2) = 1?” — confirm; standard CP convention isF(0) = 0, F(1) = 1. - “Matrix exponentiation modulus — same
peverywhere?” — yes. - “Required output format for matrices?” — only the scalar Fibonacci value, but you might be asked to return the full state vector.
Examples
power(2, 10, 1000)=24(2^10 = 1024).power(3, 0, 7)=1.power(5, 1000000000000000000, 10^9 + 7)= some specific value (must compute).fib(0, p)=0,fib(1, p)=1,fib(10, p)=55.fib(10^18, 10^9 + 7)= a specific value the engine must produce.
Initial Brute Force
Part 1: result = 1; for i in 1..b: result = result * a % m. O(b).
Part 2: iterative two-variable Fibonacci. O(n).
def power_brute(a, b, m):
result = 1 % m
for _ in range(b):
result = result * a % m
return result
def fib_brute(n, p):
if n == 0: return 0
a, b = 0, 1
for _ in range(n - 1):
a, b = b, (a + b) % p
return b
Brute Force Complexity
Part 1: O(b) mults, b = 10^18 is 10^17 mults/sec required — impossible. Part 2: same. Useful only as oracles on n ≤ 30.
Optimization Path
Part 1.
- Naive
O(b)(above). - Recursive
O(log b):power(a, b) = power(a, b/2)² if b even, else a · power(a, b-1). - Iterative
O(log b): process bits ofblow-to-high, squareaeach iteration, multiply into result when the current bit ofbis 1. Preferred for stack safety.
Part 2.
- Naive
O(n)(above). - Memoized doubling:
F(2k) = F(k) · (2 · F(k+1) − F(k)),F(2k+1) = F(k)² + F(k+1)².O(log n)recursion. Beautiful but error-prone. - Matrix exponentiation: build
M = [[1,1],[1,0]], computeM^nvia integer-fast-power lifted to matrices, extractM^n[0][1]asF(n).O(log n) · O(8)= ~500 ops forn = 10^18. The general-purpose technique that works for any linear recurrence.
Final Expected Approach
Part 1 (iterative):
long long power(long long a, long long b, long long m) {
long long res = 1 % m;
a %= m;
while (b > 0) {
if (b & 1) res = res * a % m;
a = a * a % m;
b >>= 1;
}
return res;
}
Part 2 (matrix):
typedef vector<vector<long long>> Mat;
const long long P = 1e9 + 7;
Mat matmul(const Mat &A, const Mat &B) {
int n = A.size();
Mat C(n, vector<long long>(n, 0));
for (int i = 0; i < n; ++i)
for (int k = 0; k < n; ++k)
if (A[i][k])
for (int j = 0; j < n; ++j)
C[i][j] = (C[i][j] + A[i][k] * B[k][j]) % P;
return C;
}
Mat matpow(Mat M, long long e) {
int n = M.size();
Mat res(n, vector<long long>(n, 0));
for (int i = 0; i < n; ++i) res[i][i] = 1; // identity
while (e > 0) {
if (e & 1) res = matmul(res, M);
M = matmul(M, M);
e >>= 1;
}
return res;
}
long long fib(long long n) {
if (n == 0) return 0;
Mat M = {{1, 1}, {1, 0}};
Mat R = matpow(M, n);
return R[0][1];
}
Data Structures Used
- Part 1: scalars only.
- Part 2: 2×2 matrices as
vector<vector<long long>>orarray<array<long long, 2>, 2>.
Correctness Argument
Part 1 (binary exponentiation invariant). Let b = sum b_i 2^i be the binary representation of the original exponent. After k iterations, the variable a equals a_initial^(2^k) mod m, and res equals the product of a_initial^(2^i) mod m for all i < k with b_i = 1. After all iterations, res = a_initial^b mod m. The invariant proves correctness; termination is b → 0 after floor(log₂ b) + 1 iterations.
Part 2 (matrix recurrence). Define the column vector v(k) = [F(k+1), F(k)]^T. Then M · v(k) = [[1,1],[1,0]] · [F(k+1), F(k)]^T = [F(k+1) + F(k), F(k+1)]^T = [F(k+2), F(k+1)]^T = v(k+1). By induction, v(n) = M^n · v(0) = M^n · [1, 0]^T, so F(n) = v(n)[1] = (M^n · [1, 0]^T)[1] = M^n[1][0]. Equivalently, M^n[0][1] = F(n) (by symmetry of the Fibonacci matrix). Matrix multiplication is associative, so binary exponentiation lifts directly: same invariant, just with matrices.
Complexity
- Part 1:
O(log b)time,O(1)space. - Part 2:
O(K^3 log n)time forK × Kmatrices (K = 2for Fibonacci →8 log n≈ 480 ops forn = 10^18),O(K^2)space.
Implementation Requirements
- All multiplications mod
pimmediately. - Identity matrix initialization for
matpow. - Handle
n = 0(return 0 directly, notM^0 · [1, 0]^T = [1, 0]^Twhich would giveF(0) = 0correctly anyway, but watch corner cases). - Use
long long(orint64); intermediate products are up to(10^9)² ≈ 10^18, just fittinglong long.
Tests
def test_power():
assert power(2, 10, 1000) == 24
assert power(3, 0, 7) == 1
assert power(0, 0, 7) == 1
assert power(0, 5, 7) == 0
assert power(2, 63, 10**18) == 2**63 % 10**18
def test_fib():
p = 10**9 + 7
expected = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
for i, e in enumerate(expected):
assert fib(i, p) == e
# Stress vs brute up to N = 30
for n in range(31):
assert fib(n, p) == fib_brute(n, p)
# Spot-check large
assert fib(10**18, p) == 209783453 # known value, mod 1e9+7
Follow-up Questions
- “Compute
F(n)for general linear recurrencef(n) = c1 f(n-1) + ... + ck f(n-k)?” — same matrix exponentiation, with ak×kcompanion matrix. - “Compute the number of paths of length
Lbetween two nodes in a graph?” —(adj_matrix)^L, indexed by start/end. SameO(V^3 log L)engine. - “Compute Tribonacci
T(n)inO(log n)?” — 3×3 companion matrix, otherwise identical. - “Avoid
O(K^3)per multiplication for hugeK?” — research topics: Kitamasa’s algorithm reduces toO(K^2 log N); FFT-based polynomial multiplication reduces further. Out of scope here.
Product Extension
Cryptography (RSA encryption: compute m^e mod n with e, n 2048-bit, in milliseconds — same algorithm, with bignum). Computer graphics (rotation matrix R^n for repeated rotations). Markov chain steady-state approximation (P^n for stochastic matrix P, large n). Network reachability (adj^L for paths of length L).
Language / Runtime Follow-ups
- Python: built-in
pow(a, b, m)isO(log b)and ~10× faster than a hand-rolled Python loop. For matrix power: hand-roll the multiplication; numpy is overkill atK = 2(FFI overhead exceeds compute). - Java:
BigInteger.modPowexists but is 10× slower than a hand-rolledlongloop. Use the hand-rolled version unless values exceedlong. - Go:
math/big.Int.Exp(a, b, m)is correct but slow; hand-roll for hot paths. Matrix: use 2D[][]int64arrays, notmath/big. - C++:
__int128for intermediate products ifmexceeds~3·10^9(where(long long)² mod poverflows). For standardm = 10^9 + 7, plainlong longsuffices. - JS/TS:
BigIntis correct but slow; form < 2^32, useNumbercarefully (Math.floorafter* a / m) — easy to get wrong. Matrix: same caveat.
Common Bugs
- Forgetting
res = 1 % mat start (returns1instead of0whenm = 1). - Squaring
abefore checking the bit (results in extra unused multiplication; correctness OK, perf hit). - Matrix multiplication order:
M^n = M · M · ... · M, butmatmulis non-commutative — be deliberate about left/right. - Using
intinstead oflong longfor matrix entries;(10^9)² > 2^31. - Recursive
powerblowing the stack onb = 10^18(recursion depth 60 is fine; just don’t go deeper).
Debugging Strategy
If power(a, b, m) is wrong:
- Test on small cases (
b ≤ 5) where you can verify by hand. - Print binary representation of
band confirm the bits are processed. - If correct on small
bbut wrong on large: overflow. Check thata * a % museslong long, notint.
If fib(n, p) is wrong:
- Verify
M^1 = M,M^2 = [[2,1],[1,1]],M^3 = [[3,2],[2,1]]. Each entry is a Fibonacci number. - Verify
fib(0..10)matches the canonical sequence. - If small
nworks but large doesn’t: same overflow check.
Mastery Criteria
-
Write
power(a, b, m)from blank in <2 minutes. - Write the Fibonacci matrix-exponentiation engine from blank in <10 minutes.
- Articulate the binary-exponentiation invariant in one sentence.
-
Articulate the matrix-Fibonacci recurrence (
v(n+1) = M · v(n)) in 30 seconds. - Generalize to any linear recurrence: given the recurrence, write the companion matrix in <2 minutes.
- Recognize “n up to 10^18 + linear recurrence” as the trigger for matrix exponentiation in <60 seconds.
← Lab 01 — Modular Arithmetic · Phase 7 README · Lab 03 — Sieve and Factorization →