Purpose: Compute the n-th term of a linear recurrence of order k, in O(k² log n) time — far faster than O(n) naive iteration when n is huge.
Algorithm
- Given a linear recurrence
a_i = c1·a_(i-1) + c2·a_(i-2) + ... + ck·a_(i-k)and initial termsa_0, ..., a_(k-1). - Represent the recurrence as a characteristic polynomial
f(x) = x^k - c1·x^(k-1) - ... - ck. - To find
a_n, computex^n mod f(x)using fast polynomial exponentiation (repeated squaring, with polynomial multiplication followed by reduction modulof(x)at each step). - This reduction yields a polynomial
r(x) = r0 + r1·x + ... + r(k-1)·x^(k-1)of degree less thank, satisfyingx^n ≡ r(x) (mod f(x)). - The answer is
a_n = r0·a_0 + r1·a_1 + ... + r(k-1)·a_(k-1), since the recurrence structure guarantees this linear combination correctly evaluates the shifted term.
Code
typedef vector<long long> Poly;
const long long MOD = 1e9 + 7;
Poly polyMulMod(Poly a, Poly b, Poly& f, int k) {
Poly res(a.size() + b.size() - 1, 0);
for (size_t i = 0; i < a.size(); i++)
for (size_t j = 0; j < b.size(); j++)
res[i + j] = (res[i + j] + a[i] * b[j]) % MOD;
for (int i = (int)res.size() - 1; i >= k; i--) {
if (res[i] == 0) continue;
long long coef = res[i];
for (int j = 0; j < k; j++)
res[i - k + j] = (res[i - k + j] - coef * f[j] % MOD + MOD) % MOD;
}
res.resize(k);
return res;
}
long long kitamasa(vector<long long>& c, vector<long long>& a0, long long n) {
int k = c.size();
if (n < k) return a0[n];
Poly f(k + 1); // f(x) = x^k - c1 x^(k-1) - ... - ck (f[k]=1 implicit, stored separately)
for (int i = 0; i < k; i++) f[i] = (MOD - c[k - 1 - i]) % MOD;
f[k] = 1;
Poly result = {1}, base = {0, 1};
while (n > 0) {
if (n & 1) result = polyMulMod(result, base, f, k);
base = polyMulMod(base, base, f, k);
n >>= 1;
}
result.resize(k, 0);
long long ans = 0;
for (int i = 0; i < k; i++) ans = (ans + result[i] * a0[i]) % MOD;
return ans;
}Paradigm
Divide and Conquer (fast exponentiation via repeated squaring). Computing x^n mod f(x) uses the same halving structure as fast integer exponentiation, splitting the exponent in half each round and combining via polynomial multiplication.
Complexity
- Time: O(k² log n) using naive polynomial multiplication (O(k log k log n) with FFT-based multiplication)
- Space: O(k)
Proof of Correctness
Why the recurrence corresponds to x^k ≡ c1·x^(k-1) + ... + ck (mod f(x)): By construction, f(x) = x^k - c1 x^(k-1) - ... - ck, so f(x) ≡ 0 directly encodes the recurrence relation as a polynomial identity: any power x^m satisfies x^m ≡ c1·x^(m-1) + ... + ck·x^(m-k) (mod f(x)) for m ≥ k, mirroring exactly how a_m = c1·a_(m-1) + ... + ck·a_(m-k) behaves.
Why x^n mod f(x)’s coefficients directly give a_n via the initial terms: Since reduction modulo f(x) repeatedly applies the same substitution rule that defines the sequence’s recurrence, the sequence of coefficients tracked through repeated reduction evolves in lockstep with how a_n itself would evolve under the recurrence, when “seeded” with the basis 1, x, x², ..., x^(k-1) corresponding to a_0, ..., a_(k-1). Formally, one can show by induction on n that if x^n ≡ Σ ri·x^i (mod f(x)), then a_n = Σ ri·ai — true trivially for n < k (where x^n needs no reduction, giving r_n = 1 and all others 0, correctly returning a_n itself), and preserved inductively through each multiply-and-reduce step because reduction applies exactly the recurrence’s linear substitution rule.
Why repeated squaring computes x^n mod f(x) correctly and efficiently: This is the standard fast exponentiation argument: splitting n into a binary representation and repeatedly squaring the base while conditionally multiplying into the result exactly computes x^n, and reducing modulo f(x) after every multiplication keeps all intermediate polynomials bounded to degree < k, giving O(log n) multiplication+reduction rounds, each costing O(k²) (or O(k log k) with FFT). ∎
Variants / Use Cases
- Bostan-Mori Algorithm → faster O(k log k log n) alternative avoiding full polynomial multiplication per step, using generating function relations instead
- Matrix Exponentiation → alternative O(k³ log n) method representing the same recurrence as a matrix power, simpler to implement but slower for large
k - Berlekamp-Massey + Kitamasa combo → a common competitive programming pattern: derive an unknown recurrence from computed terms via Berlekamp-Massey, then jump to a huge index via Kitamasa
- Linear recurrence-based counting problems (e.g., tiling counts, Fibonacci-like sequences at huge n) → classic application
- Cryptographic LFSR state prediction at a future time step → predicting far-future LFSR outputs efficiently