Appearance
Matrix Exponentiation
Quick Revisit
- USE WHEN: linear recurrence with n up to 10^18; state transitions expressible as matrix multiply
- INVARIANT: state vector times transition matrix raised to n equals the n-th term
- TIME: O(k^3 log n) | SPACE: O(k^2)
- CLASSIC BUG: off-by-one in exponent -- matrix power n vs n-1 depending on base case indexing
- PRACTICE: ../12-practice-ladders/08-ladder-mixed.md
AL-12 | Turn any linear recurrence into a matrix power and compute it in
Difficulty: [Intermediate]Prerequisites: Math Fundamentals, DP IntroCF Rating Range: 1700-2000
Contest Frequency: [*] Specialized -- appears in linear recurrence problems
Contents
- Intuition
- Core Mechanics
- Implementation
- Patterns and Variations
- Complexity Analysis
- Edge Cases and Pitfalls
- Worked Examples
- Self-Test
- Practice Problems
- Key Takeaways
Intuition
"Compute
The textbook DP is clean and correct -- but hopelessly slow:
cpp
// O(n) -- works for n <= 10^7, dies for n = 10^18
vector<long long> dp(n + 1);
dp[0] = 0; dp[1] = 1;
for (int i = 2; i <= n; i++)
dp[i] = (dp[i - 1] + dp[i - 2]) % MOD;At
The same wall appears whenever a recurrence has small state but astronomical index: tribonacci at
The Key Insight
Any linear recurrence
The trick is to pack the last
Applying
Analogy -- fast-forward button. The DP loop is playing a tape at 1x. Binary exponentiation lets you double the playback speed at each step:
Visual Walkthrough
Fibonacci as matrix multiplication:
The recurrence
[ F(n+1) ] [ 1 1 ] [ F(n) ]
[ ] = [ ] * [ ]
[ F(n) ] [ 1 0 ] [ F(n-1) ]State vector transformation -- one step visualized:
STATE VECTOR TRANSFORMATION: [F_n, F_{n-1}] * M = [F_{n+1}, F_n]
+--------+ +---------+ +----------+
| F_n | | 1 1 | | F_{n+1} |
| | * | | = | |
| F_{n-1}| | 1 0 | | F_n |
+--------+ +---------+ +----------+
state(n) transition M state(n+1)
How each entry is computed:
F_{n+1} = 1*F_n + 1*F_{n-1} <-- row 0 of M gives the recurrence
F_n = 1*F_n + 0*F_{n-1} <-- row 1 of M shifts the old value down
Example: [F_5, F_4] = [5, 3]
[5, 3] * [[1,1],[1,0]] = [5+3, 5+0] = [8, 5] = [F_6, F_5] OKBinary exponentiation tree -- how
BINARY EXPONENTIATION TREE FOR M^13
13 = 1101 in binary = 8 + 4 + 1
Squaring chain (compute all needed powers):
+---------+ +---------+ +---------+ +---------+
| M^1 | ---> | M^2 | ---> | M^4 | ---> | M^8 |
| (given) | sq | = M*M | sq | =(M^2)^2| sq | =(M^4)^2|
+---------+ +---------+ +---------+ +---------+
| | |
| bit 0 = 1 | bit 2 = 1 | bit 3 = 1
| (use it!) | (use it!) | (use it!)
v v v
result = M^1 result = M^1 * M^4 result *= M^8
result = M^5 result = M^13
Only 3 squarings + 2 multiplications instead of 12 multiplications!
Bit scan of 13 = 1101:
bit 0: SET --> result = M^1
bit 1: CLEAR --> skip (but still square: base = M^2)
bit 2: SET --> result = result * M^4 = M^5
bit 3: SET --> result = result * M^8 = M^13Let
Then
Walk through
Step 1: Compute powers of M by repeated squaring
--------------------------------------------------------------
M^1 = [ 1 1 ]
[ 1 0 ]
M^2 = M^1 * M^1 = [ 1*1+1*1 1*1+1*0 ] = [ 2 1 ]
[ 1*1+0*1 1*1+0*0 ] [ 1 1 ]
M^4 = M^2 * M^2 = [ 2*2+1*1 2*1+1*1 ] = [ 5 3 ]
[ 1*2+1*1 1*1+1*1 ] [ 3 2 ]
Step 2: Multiply powers whose bits are set in n = 5 = (101)_2
--------------------------------------------------------------
bit 0 (set): result = M^1 = [ 1 1 ]
[ 1 0 ]
bit 1 (clear): skip M^2
bit 2 (set): result = M^4 * result
= [ 5 3 ] * [ 1 1 ] = [ 8 5 ]
[ 3 2 ] [ 1 0 ] [ 5 3 ]
Step 3: Apply to base vector
--------------------------------------------------------------
M^5 * v0 = [ 8 5 ] * [ 1 ] = [ 8 ] --> F(6) = 8
[ 5 3 ] [ 0 ] [ 5 ] --> F(5) = 5 <-- answerCheck:
The Invariant
+-------------------------------------------------------------------+
| INVARIANT |
| |
| At each step i of binary exponentiation: |
| |
| base = M^(2^i) |
| result = M^(sum of 2^j for all set bits j < i that are set) |
| |
| After processing all bits of n: |
| result = M^n |
| |
| CORRECTNESS: M^(2^(i+1)) = (M^(2^i))^2 -- squaring is valid |
| M^n = product of M^(2^j) for set bits j in n |
| |
| COMPLEXITY: O(log n) squarings, each costs O(k^3) for k x k |
| matrices. Total: O(k^3 * log n). |
+-------------------------------------------------------------------+Convention box — Fibonacci indexing
Most off-by-one errors in matrix exponentiation come from mixing up two equivalent-but-different conventions for how the state vector and matrix power line up. Pick one and stick to it for the whole problem:
+----------------------------------------------------------------------+
| CONVENTION A (used in this file's walkthrough) |
| base vector v_0 = [F(1), F(0)]^T = [1, 0]^T |
| after k applications: M^k * v_0 = [F(k+1), F(k)]^T |
| -> to get F(n), compute M^n * v_0 and read the BOTTOM entry. |
+----------------------------------------------------------------------+
| CONVENTION B |
| base vector v_1 = [F(1), F(0)]^T but pull the power down by one |
| M^(n-1) * v_1 = [F(n), F(n-1)]^T |
| -> to get F(n), compute M^(n-1) * v_1 and read the TOP entry. |
+----------------------------------------------------------------------+
| Pick ONE. Mixing the two is the #1 source of off-by-one bugs in |
| matrix exponentiation. Test on F(0), F(1), F(2), F(3) by hand |
| before trusting the code. |
+----------------------------------------------------------------------+State-vector design — the actually hard part
Once you can multiply matrices, exponentiation is mechanical. The skill that takes practice is deciding what entries belong in the state vector. The rule is: the state must contain exactly the values needed for one matrix multiplication to advance time by one step.
Three common state-vector idioms:
- Pure recurrence of order
. State is . Top row of encodes the recurrence; the rest is a shift. - Recurrence with a polynomial inhomogeneous term (e.g.
). Augment the state with and . Each polynomial term in adds one row: a constant adds , a linear term adds , a quadratic term adds . - Adjacency matrix on a graph. State is the indicator vector of the current vertex (or distribution).
counts walks of length .
If you cannot write down "applying
When to Reach for This
Trigger phrases in problem statements:
| Trigger | Why it signals matrix exponentiation |
|---|---|
| "compute | Index too large for DP loop |
| "linear recurrence" | Direct matrix formulation |
| "number of paths of length | Adjacency matrix to the |
| " | Classic matrix speedup |
| "tiling a | State vector over column profiles |
| " | Divide-and-conquer on the sum with matrix exp |
Decision flowchart:
Is there a linear recurrence of order k (small k)?
|
+--YES--> Is n large (> ~10^7)?
| |
| +--YES--> Matrix exponentiation O(k^3 log n)
| +--NO---> Plain DP loop O(k * n)
|
+--NO---> Not applicable (consider other techniques)What the Code Won't Teach You
The matrix multiplication code is mechanical. What it obscures:
Semi-ring generalization. Standard matrix exponentiation uses combine changes.
SEMI-RING MATRIX EXPONENTIATION:
+------------------------+---------------+----------------------+
| (X, Y) semi-ring | identity for X| what A^k computes |
+------------------------+---------------+----------------------+
| (+, *) standard | 0 | # of walks of len k |
| (min,+) tropical | +inf | shortest k-edge walk|
| (max,+) | -inf | longest k-edge walk |
| (OR,AND) | false | k-step reachability |
+------------------------+---------------+----------------------+State augmentation is a modelling skill, not a code skill. When the recurrence has a constant term (
- n^2$), you augment with
. The matrix grows, but the code is unchanged -- the hard part is choosing the right state vector, and no amount of staring at operator*teaches that.
The semi-ring generalization turns matrix exponentiation into a shortest/longest path finder. Replace addition with
3. Core Mechanics
3.1 Matrix Multiplication
For two
Complexity:
Key properties that make this work:
- Associativity:
-- lets us group multiplications freely. - Identity element: the identity matrix
with , zeros elsewhere. - Not commutative:
in general -- order matters in chained multiplications.
3.2 Matrix Binary Exponentiation
Exactly like scalar binary exponentiation, but replace scalar * with matrix *:
matpow(M, n):
result = I // k x k identity matrix
base = M
while n > 0:
if n is odd:
result = result * base
base = base * base
n = n >> 1
return resultMultiplications performed: at most
BINARY EXPONENTIATION OF MATRICES -- trace for M⁹:
9 in binary: 1 0 0 1
Start: result = I (identity), base = M
bit 0 (1): result = I x M = M base = M²
bit 1 (0): result = M (no change) base = M⁴
bit 2 (0): result = M (no change) base = M⁸
bit 3 (1): result = M x M⁸ = M⁹ base = M¹⁶ (unused)
Only 2 matrix multiplies for "result x base"
Plus 3 squarings for "base x base"
Total: 5 matrix multiplies instead of 8 (naïve)
┌───────────────────────────────────────────┐
│ Exponent bits: 1 0 0 1 │
│ │ │ │
│ Multiply: xM⁸ xM¹ │
│ │ │ │
│ Result: I -> M -> ─── -> ─── -> M⁹ │
│ │
│ Squarings: M->M²->M⁴->M⁸ (3 squarings) │
│ Total ops: 3 squarings + 2 multiplies │
└───────────────────────────────────────────┘3.3 Building the Transition Matrix
Given a recurrence
State vector: s(n) = [ f(n), f(n-1), ..., f(n-k+1) ]^T
Transition matrix M (k x k):
Row 0: [ c_1 c_2 c_3 ... c_{k-1} c_k ] <-- recurrence coefficients
Row 1: [ 1 0 0 ... 0 0 ] <-- shift: f(n-1) <- f(n-1)
Row 2: [ 0 1 0 ... 0 0 ] <-- shift: f(n-2) <- f(n-2)
... ...
Row k-1: [ 0 0 0 ... 1 0 ] <-- shift: f(n-k+1) <- f(n-k+1)Example -- Tribonacci
M = [ 1 1 1 ] v0 = [ T(2) ] [ 1 ]
[ 1 0 0 ] [ T(1) ] = [ 1 ]
[ 0 1 0 ] [ T(0) ] [ 0 ]Then
Implementation
4.1 Matrix Struct
cpp
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll MOD = 1e9 + 7;
struct Matrix {
int n; // dimension (n x n)
vector<vector<ll>> a;
Matrix(int n) : n(n), a(n, vector<ll>(n, 0)) {}
// Identity matrix factory
static Matrix identity(int n) {
Matrix I(n);
for (int i = 0; i < n; i++) I.a[i][i] = 1;
return I;
}
Matrix operator*(const Matrix& o) const {
Matrix res(n);
for (int i = 0; i < n; i++)
for (int p = 0; p < n; p++) {
if (a[i][p] == 0) continue; // skip zeros early
for (int j = 0; j < n; j++)
res.a[i][j] = (res.a[i][j] + a[i][p] * o.a[p][j]) % MOD;
}
return res;
}
};Notes:
- The
p-loop is in the middle (i-p-jorder) so the inner loop streams overo.a[p][j]-- better cache behavior thani-j-p. - The
if (a[i][p] == 0) continueskip is cheap and helps on sparse matrices. - For
(most contest problems) this runs in nanoseconds per multiply.
4.2 Matrix Binary Exponentiation
cpp
Matrix matpow(Matrix base, ll exp) {
Matrix result = Matrix::identity(base.n);
while (exp > 0) {
if (exp & 1) result = result * base;
base = base * base;
exp >>= 1;
}
return result;
}4.3 Solving Fibonacci
cpp
ll fibonacci(ll n) {
if (n <= 1) return n;
Matrix M(2);
M.a = {{1, 1},
{1, 0}};
Matrix result = matpow(M, n - 1);
// result.a[0][0] = F(n), result.a[0][1] = F(n-1)
return result.a[0][0];
}Why n - 1? We start from result.a[0][0].
4.4 General Linear Recurrence Solver
cpp
// Solve f(n) = c[0]*f(n-1) + c[1]*f(n-2) + ... + c[k-1]*f(n-k)
// init[0] = f(0), init[1] = f(1), ..., init[k-1] = f(k-1)
ll solve_recurrence(vector<ll> c, vector<ll> init, ll n) {
int k = (int)c.size();
if (n < k) return init[n] % MOD;
// Build transition matrix
Matrix M(k);
for (int j = 0; j < k; j++)
M.a[0][j] = (c[j] % MOD + MOD) % MOD; // first row = coefficients
for (int i = 1; i < k; i++)
M.a[i][i - 1] = 1; // sub-diagonal = 1
Matrix result = matpow(M, n - (k - 1));
// Multiply result by initial state vector [f(k-1), f(k-2), ..., f(0)]
ll ans = 0;
for (int j = 0; j < k; j++)
ans = (ans + result.a[0][j] % MOD * (init[k - 1 - j] % MOD)) % MOD;
return (ans + MOD) % MOD;
}Usage for Fibonacci: solve_recurrence({1, 1}, {0, 1}, n)Usage for Tribonacci: solve_recurrence({1, 1, 1}, {0, 0, 1}, n)
ASCII Trace: Computing Fibonacci F(6) via Matrix Exponentiation
Recurrence: F(n) = F(n-1) + F(n-2), F(0)=0, F(1)=1
Matrix form: [F(n+1)] = [1 1]^n x [F(1)] = M^n x [1]
[F(n) ] [1 0] [F(0)] [0]
Goal: compute M^6 using binary exponentiation. 6 = 110₂
Initialize: result = I = [1 0] base = M = [1 1]
[0 1] [1 0]
Step 1: bit=0 (LSB of 6=110₂) -> 0, skip multiply
base = M² = [1 1] x [1 1] = [2 1]
[1 0] [1 0] [1 1]
Step 2: bit=1 -> 1, result *= base
result = I x M² = [2 1]
[1 1]
base = M⁴ = [2 1] x [2 1] = [5 3]
[1 1] [1 1] [3 2]
Step 3: bit=1 (MSB) -> 1, result *= base
result = [2 1] x [5 3] = [13 8]
[1 1] [3 2] [ 8 5]
result = M⁶ = [13 8]
[ 8 5]
Answer: F(6) = result[1][0] = 8 OK (0,1,1,2,3,5,8)
F(7) = result[0][0] = 13 OK5. Patterns and Variations
Pattern 1: Fibonacci / -bonacci
The most common entry point. Recurrence order
| Recurrence | Transition matrix size | Time | |
|---|---|---|---|
| Fibonacci | 2 | ||
| Tribonacci | 3 | ||
| Tetranacci | 4 |
Pattern 2: Recurrence with Constant Term
Suppose
State: [ f(n), 1 ]^T
M = [ 2 3 ] Meaning: f(n) = 2*f(n-1) + 3*1
[ 0 1 ] 1 = 0*f(n-1) + 1*1Works for any polynomial additive term by adding more dummy dimensions (
STATE AUGMENTATION -- ADDING A CONSTANT TERM:
Recurrence: f(n) = 2*f(n-1) + 3 (constant +3)
Without augmentation: CANNOT express as matrix multiply
With augmentation: add a "1" that stays 1 forever
+----------+ +---------+ +----------+
| f(n+1) | | 2 3 | | f(n) |
| | = | | * | |
| 1 | | 0 1 | | 1 |
+----------+ +---------+ +----------+
f(3): start [f(1),1]=[1,1] -> M*[1,1]=[5,1] -> M*[5,1]=[13,1]
Check: f(2)=2*1+3=5, f(3)=2*5+3=13 (correct)Pattern 3: Tiling Problems
"Count the ways to tile a
The state is the column profile -- which cells in the current column are already covered by a horizontal domino started in the previous column. For a
COLUMN PROFILE DP FOR TILING:
3*n grid, 1*2 dominoes. State = which cells in column c are
already covered by a horizontal domino from column c-1.
Column c-1 Column c Profile for col c
+---+ +---+
| # |------->| . | bit 0: covered (#->.)
| | | | bit 1: empty
| # |------->| . | bit 2: covered (#->.)
+---+ +---+
Profile = 101 (binary) = cells 0,2 pre-covered
Transition matrix M[from_profile][to_profile] = 1 if valid
Answer = M^n applied to initial empty profile (all zeros)Pattern 4: Counting Paths of Length in a Graph
Theorem: If
cpp
// Count paths of length k from src to dst in a graph with n vertices
ll count_paths(vector<vector<int>>& adj, int n, int src, int dst, ll k) {
Matrix A(n);
for (int u = 0; u < n; u++)
for (int v : adj[u])
A.a[u][v] = (A.a[u][v] + 1) % MOD;
Matrix result = matpow(A, k);
return result.a[src][dst];
}Complexity:
PATH COUNTING VIA MATRIX POWER:
Graph: Adjacency matrix A:
0 ──-> 1 │ 0 1 1 │
│ ↗│ │ 0 0 1 │
↓ ╱ ↓ │ 1 0 0 │
2 <-──
Paths of length 2 from 0:
0->1->2, 0->2->0 A² = │ 1 0 1 │
│ 1 0 0 │
│ 0 1 1 │
A²[0][0] = 1: path 0->2->0
A²[0][2] = 1: path 0->1->2
Paths of length 3 from 0: A³ = A² x A = │ 0 2 1 │
│ 0 1 1 │
│ 1 0 1 │
A³[0][1] = 2: paths 0->1->2->? ...
(0->2->0->1 and 0->1->2->? -> actually need to verify)
┌──────────────────────────────────────────────┐
│ A^k[i][j] = number of directed paths of │
│ EXACTLY k edges from i to j. │
│ │
│ Works because matrix multiply sums over │
│ all possible intermediate vertices: │
│ (A^k)[i][j] = Σ_m (A^{k-1})[i][m] * A[m][j]│
└──────────────────────────────────────────────┘Pattern 5: Sum of a Geometric Matrix Series
Compute
Divide-and-conquer approach:
- If
is even: - If
is odd:
Or use the augmented-matrix trick -- build a
T = [ M I ] Then T^n = [ M^n S(n) ]
[ 0 I ] [ 0 I ]Extract the top-right
Complexity Analysis
| Operation | Time | Space |
|---|---|---|
| Matrix multiply ( | ||
| Matrix exponentiation | ||
| General recurrence of order | ||
| Graph paths of length |
Practical limits in contests (within ~1 second):
k (matrix size) | Max n (approx)
-----------------+------------------
2 | 10^18 (easily)
3 | 10^18
10 | 10^18
50 | 10^15
100 | 10^12
200 | 10^9 (tight)The bottleneck is the
Edge Cases and Pitfalls
Pitfall 1: Off-by-One in Exponent
The most common bug. If your base vector is
Wrong: matpow(M, n) --> gives f(n + k - 1)
Right: matpow(M, n - k + 1) --> gives f(n)Always test with small values: compute
Pitfall 2: Modular Arithmetic in Matrix Multiply
Every intermediate sum must be reduced mod long long. But summing
- Reduce after each addition (done in the code above).
- Or use
__int128for the accumulator and reduce once at the end.
Pitfall 3: Edge Case
exp = 0 correctly -- the while (exp > 0) loop naturally returns result = I, which is correct.
Pitfall 4: Negative Coefficients
If the recurrence has negative coefficients (e.g., MOD before taking % to avoid negative entries in the matrix:
cpp
M.a[0][j] = (c[j] % MOD + MOD) % MOD;Mental Traps
"Matrix exponentiation is only for Fibonacci-like problems." Fibonacci is the simplest instance. The technique applies whenever the state can be packed into a vector and the transition is a linear map: path counting in graphs (
MATRIX EXPONENTIATION -- WIDER THAN YOU THINK:
Problem type State size k Matrix meaning
────────────────────── ──────────── ──────────────────
Fibonacci / k-bonacci 2-4 recurrence coefficients
f(n) = af(n-1) + c 3 augmented with constant
Tiling m*n grid 2^m column-profile DP
Path count in graph |V| adjacency matrix
Shortest k-edge path |V| (min,+) adjacency"
Trap: "State size
It's
STATE SIZE vs RUNTIME:
n = 10¹⁸, log₂(n) ~= 60
k │ k³ x 60 │ Status
─────┼─────────────┼──────────
2 │ 480 │ trivial
5 │ 7,500 │ fast
10 │ 60,000 │ fast
50 │ 7,500,000 │ ok
100 │ 60,000,000 │ tight (1-2s)
200 │480,000,000 │ TLE X
┌─────────────────────────────────────────┐
│ Rule of thumb: k <= 70 is always safe. │
│ k > 100: profile before submitting. │
│ k > 200: look for a different approach │
│ (Berlekamp-Massey, Kitamasa, etc.) │
└─────────────────────────────────────────┘Trap: "If the recurrence has
Only if
STATE AUGMENTATION FOR NON-CONSTANT TERMS:
Recurrence: f(n) = 2*f(n-1) + n
State: [f(n), n, 1]ᵀ
Transition:
f(n+1) = 2*f(n) + (n+1) = 2*f(n) + n + 1
n+1 = n + 1
1 = 1
Matrix:
┌ ┐ ┌ ┐ ┌ ┐
│ f(n+1) │ │ 2 1 1 │ │ f(n) │
│ n+1 │ = │ 0 1 1 │ x │ n │
│ 1 │ │ 0 0 1 │ │ 1 │
└ ┘ └ ┘ └ ┘
Check: f(n+1) = 2*f(n) + 1*n + 1*1 = 2f(n) + n + 1 OK
n+1 = 0*f(n) + 1*n + 1*1 = n + 1 OK
1 = 0*f(n) + 0*n + 1*1 = 1 OKWorked Examples
Example 1: Fibonacci,
Problem: Compute
cpp
int main() {
ll n;
cin >> n;
cout << fibonacci(n) << "\n";
return 0;
}Using the fibonacci function from Section 4.3.
Trace for
M = [ 1 1 ] matpow(M, 9):
[ 1 0 ]
9 = 1001 in binary
result starts as I
bit 0 (set): result = I * M = M
bit 1 (clear): base = M^2, skip
bit 2 (clear): base = M^4, skip
bit 3 (set): result = M^8 * M = M^9
M^9 = [ 55 34 ]
[ 34 21 ]
F(10) = M^9[0][0] = 55. Correct.Example 2: Counting Paths of Length
Problem: Given a directed graph with
cpp
int main() {
int n, m;
ll k;
cin >> n >> m >> k;
Matrix A(n);
for (int i = 0; i < m; i++) {
int u, v;
cin >> u >> v;
u--; v--;
A.a[u][v] = (A.a[u][v] + 1) % MOD;
}
Matrix result = matpow(A, k);
cout << result.a[0][n - 1] << "\n";
return 0;
}Example 3: Recurrence with Constant Term
Problem:
Augment the state to handle the constant:
cpp
int main() {
ll n;
cin >> n;
if (n == 0) { cout << 1; return 0; }
if (n == 1) { cout << 2; return 0; }
// State: [f(n), f(n-1), 1]^T
// Transition:
// f(n) = 3*f(n-1) + 2*f(n-2) + 5*1
// f(n-1) = 1*f(n-1) + 0*f(n-2) + 0*1
// 1 = 0*f(n-1) + 0*f(n-2) + 1*1
Matrix M(3);
M.a = {{3, 2, 5},
{1, 0, 0},
{0, 0, 1}};
Matrix R = matpow(M, n - 1);
// base vector: [f(1), f(0), 1] = [2, 1, 1]
ll ans = (R.a[0][0] * 2 % MOD + R.a[0][1] * 1 % MOD + R.a[0][2] * 1 % MOD) % MOD;
cout << ans << "\n";
return 0;
}Self-Test
- [ ] Write the
matrix multiplication with correct index order and % MOD-- no peeking - [ ] Construct the transition matrix for
(constant term) -- what is the matrix dimension? - [ ] Given a 3-node directed graph, compute the number of walks of length 4 from node 0 to node 2 by hand using
- [ ] Explain why
(identity) and what goes wrong if your code initializes resultto all-zeros instead - [ ] State the practical size limit: for which
does fit in 1 second? - [ ] Construct the
transition matrix for the recurrence -- write out the augmented state vector and verify the matrix by computing from . - [ ] Count the number of paths of length 4 from vertex 0 to vertex 2 in a 3-node directed graph with adjacency matrix
-- compute by hand (or by repeated squaring). - [ ] Compute
(10th Fibonacci number) using matrix exponentiation: write with the 2x2 Fibonacci matrix, using binary expansion . - [ ] Explain how to encode the recurrence
as matrix exponentiation -- what is the state vector and the matrix size? - [ ] Modify the matrix multiplication to use the
semi-ring and compute the shortest 3-edge path between all pairs in a 3-node graph with edge weights.
Practice Problems
| # | Problem | Source | Key Idea | Difficulty |
|---|---|---|---|---|
| 1 | Fibonacci | CSES | Direct | CF ~1600 |
| 2 | Graph Paths I | CSES | Adjacency matrix to the | CF ~1700 |
| 3 | Graph Paths II | CSES | Shortest path of exactly | CF ~1900 |
| 4 | C. Neko does Maths | CF Div 2 | Recurrence acceleration | CF ~1800 |
| 5 | E. The Number of Inversions | CF Div 1 | Matrix exponentiation on DP state | CF ~2000 |
| 6 | D. Magic Gems | CF Div 2 | Linear recurrence, | CF ~1700 |
| 7 | E. Pchelyonok and Segments | CF Div 2 | Matrix exponentiation + combinatorics | CF ~2000 |
| 8 | B. Parity of Inversions | CF Div 2 | Matrix exponentiation trick | CF ~1800 |
Recommended progression: Start with CSES Fibonacci (#1), then Graph Paths I (#2) to see the adjacency matrix pattern. Move to Magic Gems (#6) for general recurrences, then tackle the harder CF problems.
10. Key Takeaways
+-------------------------------------------------------------------+
| CHEAT SHEET -- MATRIX EXPONENTIATION |
| |
| 1. Identify a linear recurrence of order k |
| f(n) = c1*f(n-1) + c2*f(n-2) + ... + ck*f(n-k) |
| |
| 2. Build the k x k transition matrix M |
| - Row 0: recurrence coefficients [c1, c2, ..., ck] |
| - Rows 1..k-1: identity shift (1 on sub-diagonal) |
| |
| 3. Build the initial state vector v0 |
| v0 = [f(k-1), f(k-2), ..., f(0)]^T |
| |
| 4. Compute M^(n - k + 1) * v0 via binary exponentiation |
| - The top entry of the result is f(n) |
| |
| 5. Extensions: |
| - Constant term --> augment state with dummy "1" |
| - Graph paths --> A^k where A = adjacency matrix |
| - Tiling --> column-profile DP, matrix exp on states |
| - Matrix sum --> augmented block matrix trick |
| |
| COMPLEXITY: O(k^3 * log n) (k = state size, n = target index) |
+-------------------------------------------------------------------+One-sentence summary: Pack your DP state into a vector, write the transition as a matrix, and binary-exponentiate your way from
The Aha Moment
If you can write:
The real "aha": the hard part isn't the exponentiation -- it's formulating the state vector and transition matrix. Once you write
Pattern Fingerprint
| Constraint / Signal | Technique |
|---|---|
| "Compute | Matrix exponentiation |
| " | |
| "Count paths of length | Adjacency matrix to the |
| DP with transitions like | Pack into state vector, add constant as extra dimension |
| "Number of tilings / strings of length | Build DFA/NFA transition matrix, exponentiate |
| " | Matrix exponentiation is the go-to |
Rating Progression
| CF Rating | What You Should Be Able To Do |
|---|---|
| 1200 | Know Fibonacci can be computed in |
| 1500 | Formulate a |
| 1800 | Handle recurrences with constant terms (extend state vector); count graph paths via adjacency matrix power |
| 2100 | Build transition matrices from DFA/NFA; combine matrix exponentiation with segment tree; handle block-diagonal optimizations |
Before You Code Checklist
- [ ] Write the recurrence on paper first -- identify the state: what values do you need to carry from step
to step ? - [ ] Initialize the identity matrix correctly --
, all others ; a wrong identity gives wrong results silently - [ ] Zero-initialize the product matrix in
multiply()-- forgetting this is the #1 bug - [ ] Handle the constant term -- if
, add a "1" to the state vector and an extra row/column for the constant - [ ] Check matrix dimensions -- the state vector size
determines matrices; off-by-one in corrupts everything
What Would You Do If...?
Scenario 1: "The recurrence has a non-constant coefficient, e.g.,
Scenario 2: "The matrix is
Scenario 3: "You need
The Mistake That Teaches
Story: You need the
After debugging, you print the identity matrix your mat_pow starts with... and it's all zeros. You initialized result as a zero matrix instead of the identity matrix. The exponentiation computed
The fix:
cpp
Matrix result;
// MUST initialize as identity
for (int i = 0; i < k; i++) result.a[i][i] = 1;Small test cases accidentally passed because your base case handling returned v0 directly for
Lesson: Always verify your identity matrix initialization, and test with
Debug This
Bug 1:
Find the bug in this matrix multiplication
cpp
struct Matrix {
long long a[MAXK][MAXK];
};
Matrix multiply(Matrix &A, Matrix &B) {
Matrix C;
for (int i = 0; i < k; i++)
for (int j = 0; j < k; j++)
for (int p = 0; p < k; p++)
C.a[i][j] = (C.a[i][j] + A.a[i][p] * B.a[p][j]) % MOD;
return C;
}Bug: Matrix C; is declared but never zero-initialized. C.a[i][j] contains garbage values, so the accumulation C.a[i][j] + ... produces wrong results. In C++, local struct members are not zero-initialized by default.
Fix: Add Matrix C = {}; or memset(C.a, 0, sizeof(C.a)); before the loops.
Bug 2:
Find the bug in this matrix exponentiation
cpp
Matrix mat_pow(Matrix A, long long n) {
Matrix result;
memset(result.a, 0, sizeof(result.a));
for (int i = 0; i < k; i++) result.a[i][i] = 1; // identity
while (n) {
if (n & 1) result = multiply(result, A);
A = multiply(A, A);
n >>= 1;
}
return result;
}
// Fibonacci: F(n) = F(n-1) + F(n-2)
// A = {{1,1},{1,0}}, v = {F(1), F(0)} = {1, 0}
// Answer: (mat_pow(A, n) * v)[0]Bug: For [1] (second component), not [0].
Fix: Return (mat_pow(A, n) * v)[1] to get
Bug 3:
Find the bug in this recurrence-to-matrix conversion
cpp
// Recurrence: f(n) = 2*f(n-1) + 3*f(n-2) + 5
// State vector: [f(n), f(n-1)]
// Transition matrix:
long long A[2][2] = {
{2, 3},
{1, 0}
};Bug: The recurrence has a constant term (+5), but the matrix doesn't account for it. A
Fix: Extend the state vector to
cpp
long long A[3][3] = {
{2, 3, 5}, // f(n) = 2*f(n-1) + 3*f(n-2) + 5*1
{1, 0, 0}, // f(n-1) = 1*f(n-1)
{0, 0, 1} // 1 = 1 (constant stays constant)
};Common Mistakes
- Off-by-one in the exponent. If your base vector holds f(k-1)..f(0), you need M^(n-k+1), not M^n.
- Forgetting the constant term. Recurrences with additive constants need an extra dimension (append a 1 to the state vector).
- Integer overflow in matrix multiply. Each cell accumulates k products of large values -- intermediate sums overflow even with
long longif you don't reduce mod p at each step. - Wrong identity matrix for base case. M^0 must be the identity matrix, not the zero matrix.
Historical Origin
Matrix exponentiation as a computational technique piggybacks on two ideas: Arthur Cayley's 1858 formalization of matrix algebra, and the ancient binary exponentiation method (known in India by 200 BCE for scalar powers). The specific application to linear recurrences became a competitive programming staple in the early 2000s, popularized by problems on TopCoder and Codeforces that featured
Mnemonic Anchor
"Pack the state, build the matrix, square your way to
."
Recap and Cross-Links
| Concept | Link |
|---|---|
| Math Fundamentals | 01-math-fundamentals |
| DP Intro | 02-dp-intro-1d |
| Gaussian Elimination | 10-gaussian-elimination |
| Practice | Ladder - Mixed |