# Matrix Exponentials (When DP is Naive, Use Linear Algebra) Yep, you read that absolutely right: there exist problems where your clever dynamic programming solution is actually the **naive** solution. Don’t believe me? Try solving this Fibonacci problem, https://cses.fi/problemset/task/1722 with DP & lmk if you get DP or not. --- ## 0) **Introduction** So what the hell is matrix exponentiation and why do CP grinders love it? First step: understand **linear recurrences**. --- ### **Linear Recurrence Refresher** A **recurrence relation** defines each term of a sequence from previous ones. A **linear recurrence** means the next term is a **linear combination** of the last $k$ terms: $$ \boxed{\displaystyle f(n) = c_1 f(n-1) + c_2 f(n-2) + \cdots + c_k f(n-k).} $$ - **Linear** = only “coefficient $\times$ old term” plus additions; no squaring, no $\min/\max$. - The number of past terms ($k$) is the **order**. - Once you know the first $k$ values, the rest is determined. **Classic examples** - Fibonacci: $F(n)=F(n-1)+F(n-2)$. - Tribonacci: $T(n)=T(n-1)+T(n-2)+T(n-3)$. - Weighted: $a(n)=2\,a(n-1)-a(n-2)$. **Non-examples (nonlinear)** - $f(n)=f(n-1)\cdot f(n-2)$. - $f(n)=f(n-1)^2$. - $f(n)=\min\{f(n-1), f(n-2)\}$. **Big picture:** future $=$ weighted sum of past. **Note:** All linear recurrence problems are DP problems -> you can optimize with matrix exponentials. But not all DP problems have a linear recurrence structure. --- ### **Why Matrix Exponentiation?** Linear recurrences can be written as a matrix multiplying a **state vector**. Instead of computing $f(n)$ step by step (which dies at $n\sim10^{12}$), raise the matrix to a power with **binary exponentiation** and jump straight there. **Pipeline:** recurrence $\to$ state vector $\to$ transition matrix $\to$ fast exponentiation $\to$ result. **What is Matrix Exponentiation?** Take a linear recurrence/DP and rewrite it as $$ \text{State}(n) = T\,\text{State}(n-1). $$ Then jump to $n$ by computing $T^{n-1}$ (or $T^n$, see indexing below). **Why Is It Useful?** If you only need the $n$‑th value and $n$ is massive (e.g., $10^{12}$), simulating every step **dies in brute**. Raising $T$ with fast (binary) exponentiation is easy. --- ## 1) **Speed First: Why Binary Exponentiation Is Stupid Fast** **Goal:** compute $T^p$ without multiplying by $T$ $p$ times. $$ T^{15} = T^{8}\,T^{4}\,T^{2}\,T^{1}. $$ (Powers of the **same** matrix commute, so order doesn’t matter.) **Why it’s fast.** You do $O(\log p)$ squarings and at most $O(\log p)$ multiplies to combine them. With naive $k\times k$ multiply at $O(k^3)$, total is **$O(k^3\log p)$**. Rule of thumb: good for $k\lesssim 50$ per test (constraints matter). **Minimal code** ```cpp Matrix mpow(Matrix base, long long p) { Matrix ans(base.n, base.mod, /*identity=*/true); while (p > 0) { if (p & 1LL) ans = ans * base; // include this power if current bit is 1 base = base * base; // square: T -> T^2 -> T^4 -> ... p >>= 1; // shift exponent right } return ans; } ``` --- ## 2) **From DP to Linear Algebra (General Recipe)** Start with a $k$‑term linear recurrence: $$ \displaystyle f(n) = c_1 f(n-1) + c_2 f(n-2) + \cdots + c_k f(n-k). $$ **State vector** = just enough info to advance one step: $$ \text{State}(n) = \begin{bmatrix} f(n)\\ f(n-1)\\ \vdots \\ f(n-k+1) \end{bmatrix}. $$ There’s a $k\times k$ **transition (companion) matrix** $T$ such that $$ T=\begin{bmatrix} c_1 & c_2 & \cdots & c_{k-1} & c_k\\ 1 & 0 & \cdots & 0 & 0\\ 0 & 1 & \cdots & 0 & 0\\ \vdots & \vdots & \ddots & \vdots & \vdots\\ 0 & 0 & \cdots & 1 & 0 \end{bmatrix}, \qquad \text{State}(n) = T\,\text{State}(n-1). $$ Unroll: $$ \text{State}(n) = T^{\,n-1}\,\text{State}(1). $$ **When is it $T^n$ vs $T^{n-1}$?** - Base at **step 1** (you know $\text{State}(1)$) $\Rightarrow$ $\text{State}(n)=T^{n-1}\,\text{State}(1)$. - Base at **step 0** (you know $\text{State}(0)$) $\Rightarrow$ $\text{State}(n)=T^{n}\,\text{State}(0)$. It’s just indexing. Align it with your base case. **Don’t overthink**—pick the natural base. --- ## 3) **Worked Example: Fibonacci (2×2)** Recurrence: $F(n)=F(n-1)+F(n-2)$, with $F(0)=0,\;F(1)=1$. **State and Transition** $$ \text{State}(n) = \begin{bmatrix} F(n) \\ F(n-1) \end{bmatrix},\qquad T = \begin{bmatrix} 1 & 1 \\ 1 & 0 \end{bmatrix}. $$ **Check** $$ T \begin{bmatrix} F(n-1) \\ F(n-2) \end{bmatrix} = \begin{bmatrix} F(n-1)+F(n-2) \\ F(n-1) \end{bmatrix} = \begin{bmatrix} F(n) \\ F(n-1) \end{bmatrix}. $$ So the recurrence matches. **Base case and small test** Pick base $$ \text{State}(1)=\begin{bmatrix}1\\0\end{bmatrix}, $$ so $$ \text{State}(n)=T^{n-1}\,\text{State}(1). $$ Small check: $$ T^4=\begin{bmatrix}5&3\\3&2\end{bmatrix} \;\;\Rightarrow\;\; F(5)=5. $$ ### **Fibonacci code (clean)** ```cpp #include <iostream> #include <vector> using namespace std; using ll = long long; const long long MOD = 1'000'000'007LL; struct Matrix { int n; ll mod; vector<vector<ll>> a; Matrix(int n, ll mod, bool ident = false) : n(n), mod(mod), a(n, vector<ll>(n, 0)) { if (ident) for (int i = 0; i < n; ++i) a[i][i] = 1; } Matrix operator*(const Matrix& o) const { Matrix r(n, mod); for (int i = 0; i < n; ++i) { for (int k = 0; k < n; ++k) if (a[i][k]) { ll aik = a[i][k]; for (int j = 0; j < n; ++j) { r.a[i][j] = (r.a[i][j] + aik * o.a[k][j]) % mod; } } } return r; } }; Matrix mpow(Matrix base, long long p) { Matrix ans(base.n, base.mod, true); while (p > 0) { if (p & 1LL) ans = ans * base; base = base * base; p >>= 1; } return ans; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); long long n; cin >> n; if (n == 0) { cout << 0 << "\n"; return 0; } if (n == 1) { cout << 1 << "\n"; return 0; } Matrix T(2, MOD); T.a = {{1, 1}, {1, 0}}; Matrix P = mpow(T, n - 1); // State(n) = P * State(1), State(1) = [1, 0]^T // F(n) is the top component -> top-left entry of P cout << (P.a[0][0] % MOD) << "\n"; return 0; } ``` --- ## 4) **Another Example: Graph Paths (Exact Length $k$)** **Task:** number of length‑$k$ paths from node $1$ to node $n$ in a directed (possibly multi‑edge) graph. ### Naive DP (works, but dies if $k$ is huge) Let $$ \begin{aligned} &dp[\ell][u] := \#\{\text{paths of length }\ell\text{ from }1\text{ to }u\},\\ &dp[0][1]=1,\quad dp[0][u\ne1]=0. \end{aligned} $$ Transition: $$ dp[\ell+1][u] \;=\; \sum_{v} dp[\ell][v]\cdot \#\text{edges}(v\to u). $$ ### Adjacency Matrix View Build $A$ with **row = from, col = to**: $$ A[v][u] := \#\text{edges}(v\to u). $$ Treat $dp[\ell]$ as a **row vector** $[dp[\ell][1],\dots,dp[\ell][n]]$. Then $$ dp[\ell+1] = dp[\ell]\,A,\qquad dp[k] = dp[0] \; A^k,\qquad dp[0] = e_1^\top = [1,0,\dots,0]. $$ Answer for paths $1\to n$ of length $k$ is $$ dp[k][n] = (A^k)[1][n] \text{ in 1-indexed math} \;\Longleftrightarrow\; (A^k)[0][n-1] \text{ in 0-indexed code}. $$ ### **Graph Paths Code** ```cpp #include <iostream> #include <vector> using namespace std; using ll = long long; const ll MOD = 1'000'000'007LL; struct Matrix { int n; ll mod; vector<vector<ll>> a; Matrix(int n, ll mod, bool ident = false) : n(n), mod(mod), a(n, vector<ll>(n, 0)) { if (ident) for (int i = 0; i < n; ++i) a[i][i] = 1; } Matrix operator*(const Matrix& o) const { Matrix r(n, mod); for (int i = 0; i < n; ++i) { for (int k = 0; k < n; ++k) if (a[i][k]) { ll aik = a[i][k]; for (int j = 0; j < n; ++j) { r.a[i][j] = (r.a[i][j] + aik * o.a[k][j]) % mod; } } } return r; } }; Matrix mpow(Matrix base, long long p) { Matrix ans(base.n, base.mod, true); while (p > 0) { if (p & 1LL) ans = ans * base; base = base * base; p >>= 1; } return ans; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n, m; long long k; cin >> n >> m >> k; Matrix A(n, MOD); // A[from][to] for (int i = 0; i < m; ++i) { int from, to; // edge from -> to, 1-indexed input cin >> from >> to; --from; --to; A.a[from][to] = (A.a[from][to] + 1) % MOD; // row=from, col=to } Matrix Ak = mpow(A, k); // dp[0] = e1^T picks the first row when multiplying on the left: // dp[k][n] = (e1^T * A^k)[n] = (A^k)[0][n-1] cout << Ak.a[0][n-1] << "\n"; return 0; } ``` **Why multi‑edges become counts:** each parallel edge is a distinct choice in a path, so it adds $+1$ into $A[\text{from}][\text{to}]$. --- ## 5) **How to Define the State & Transition (Formal Derivation)** ### 5.1 General $k$‑term linear recurrence Given $$ f(n) = c_1 f(n-1) + c_2 f(n-2) + \cdots + c_k f(n-k), $$ define the **state** $$ S(n)=\begin{bmatrix} f(n)\\ f(n-1)\\ \vdots \\ f(n-k+1) \end{bmatrix}. $$ Then $$ S(n) = \underbrace{\begin{bmatrix} c_1 & c_2 & \cdots & c_{k-1} & c_k\\ 1 & 0 & \cdots & 0 & 0\\ 0 & 1 & \cdots & 0 & 0\\ \vdots & \vdots & \ddots & \vdots & \vdots\\ 0 & 0 & \cdots & 1 & 0 \end{bmatrix}}_{T} \, S(n-1),\qquad S(n)=T^{\,n-n_0}S(n_0). $$ Pick $n_0$ so all entries of $S(n_0)$ are known (e.g., $n_0=k$ if you’re given $f(1\dots k)$). --- ### 5.2 Fibonacci $$ S(n)=\begin{bmatrix}F(n)\\ F(n-1)\end{bmatrix},\quad T=\begin{bmatrix}1&1\\ 1&0\end{bmatrix},\quad S(n)=T\,S(n-1),\quad S(1)=\begin{bmatrix}1\\0\end{bmatrix}. $$ --- ### 5.3 Tribonacci **Recurrence & Bases** $$ \mathrm{Tri}(n)=\mathrm{Tri}(n-1)+\mathrm{Tri}(n-2)+\mathrm{Tri}(n-3),\quad \mathrm{Tri}(0)=0,\;\mathrm{Tri}(1)=0,\;\mathrm{Tri}(2)=1. $$ **State and Transition** $$ S(n)=\begin{bmatrix} \mathrm{Tri}(n)\\ \mathrm{Tri}(n-1)\\ \mathrm{Tri}(n-2) \end{bmatrix},\qquad T=\begin{bmatrix} 1&1&1\\ 1&0&0\\ 0&1&0 \end{bmatrix},\qquad S(n)=T\,S(n-1). $$ Take $S(2)=[1,0,0]^\top$, then $S(n)=T^{\,n-2}S(2)$. **Compute $S(7)$ ($\mathrm{Tri}(7)$)** Squares: $$ T^2=\begin{bmatrix}2&2&1\\1&1&1\\1&0&0\end{bmatrix},\quad T^4=(T^2)^2=\begin{bmatrix}7&6&4\\4&3&2\\2&2&1\end{bmatrix},\quad T^5=T^4T=\begin{bmatrix}13&11&7\\7&6&4\\4&3&2\end{bmatrix}. $$ Jump: $$ S(7)=T^5 S(2)=\text{first column of }T^5= \begin{bmatrix}13\\7\\4\end{bmatrix} \Rightarrow \mathrm{Tri}(7)=13. $$ --- ### 5.4 Graph Paths Define $$ dp[\ell][u] = \#\{\text{paths of length }\ell\text{ from }1\text{ to }u\}. $$ Let the **state at layer $\ell$** be the **row** vector $$ S(\ell)\equiv dp[\ell] = \begin{bmatrix} dp[\ell][1] & dp[\ell][2] & \cdots & dp[\ell][n] \end{bmatrix}. $$ Build $A$ with **row = from, col = to**: $$ A[v][u]=\#\text{edges}(v\to u). $$ Then $$ S(\ell+1)=S(\ell)\,A,\qquad S(0)=e_1^\top=[1,0,\dots,0], $$ so $$ S(k)=e_1^\top A^k,\quad \#\text{paths }1\to n\text{ of length }k \;=\; (A^k)[1][n] \text{ (1‑index)} \;=\; (A^k)[0][n-1] \text{ (0‑index)}. $$ --- ## 6) **Closing Notes (Pattern + Caveats)** ### **Pattern** 1. **Define state.** Package just enough info into a vector so one step forward is linear. 2. **Build transition $T$.** Write the recurrence as a dot product of coefficients with past state. 3. **Choose base index.** Decide if your base is $\text{State}(0)$ or $\text{State}(1)$. 4. **Compute $T^{\text{power}}$** with **binary exponentiation** — $O(k^3\log n)$. 5. **Multiply once by the base vector.** That gives $\text{State}(n)$. ### **How to Spot it From a Statement** - The next answer is a **weighted sum** of the last $k$ answers (not min/max/product/square). - **Coefficients are constant** in $n$ (fixed transition). - Graph path DP that looks like $$dp[\ell+1][u] = \sum_v dp[\ell][v] \cdot \text{edges}(v\to u)$$ is literally matrix multiplication with your $A[v][u]$. - If $n$ or $k$ goes up to $10^9$, $10^{12}$, $10^{18}$, step‑by‑step DP is impossible — use matrix expo. ### **Where It Shines** - Fibonacci/Tribonacci/**any** fixed $k$‑term linear recurrence. - Population/Markov models. - Graph path counts of exact length $k$. - Automata DP (counting strings under constraints). ### **Caveats** - Always mod after add/mul; use 64‑bit intermediates before mod. - $O(k^3\log n)$ can die when $k$ is large (and squaring densifies matrices). - Don’t overkill: if $n$ is small, plain DP may be faster/simpler. **Aside.** For 1‑D recurrences there’s also the **Kitamasa method**, which targets $f(n)$ without building a full $k\times k$ matrix; out of scope here. **TLDR** If the problem screams “linear recurrence + huge $n$,” DP is naive. Answer = define state $\to$ build transition $\to$ fast power. Enjoy mogging everyone else. --- ## Recommended Problems * [USACO Guide - Matrix Exponentiation](https://usaco.guide/plat/matrix-expo) * [UCLA ICPC Spring Training #4 Contest](https://vjudge.net/contest/628365)