If the state vector evolves by a fixed linear map, , then and up to costs for states. The same works in any semiring: gives shortest walks with exactly edges, bottleneck walks, reachability in exactly steps. Matrix arithmetic, determinants and fast recurrence methods are in Matrices and Linear Recurrences.
using Mat = vector<vector<ll>>; const ll MOD = 1e9 + 7;
Mat mul(const Mat& a, const Mat& b) {
int n = sz(a), m = sz(b[0]), K = sz(b); Mat c(n, vector<ll>(m));
for (int i = 0; i < n; i++) for (int k = 0; k < K; k++) if (a[i][k]) for (int j = 0; j < m; j++) c[i][j] = (c[i][j] + a[i][k] * b[k][j]) % MOD;
return c;
}
Mat power(Mat a, ll e) { Mat r(sz(a), vector<ll>(sz(a))); for (int i = 0; i < sz(a); i++) r[i][i] = 1; for (; e; e >>= 1, a = mul(a, a)) if (e & 1) r = mul(r, a); return r; }(Loop order walks rows contiguously; for with a modulus, accumulate in unsigned __int128 or reduce every 16 terms with ull to save the inner %.)
Building the matrix
| Recurrence | State vector |
|---|---|
| , companion matrix | |
| constant | append a constant |
| polynomial | append ; update with binomial coefficients |
| geometric | append (multiplied by each step) |
| prefix sums | append |
| several interleaved sequences | stack them |
| periodic transitions | exponentiate , then apply the remainder |
| walks of length in a graph | adjacency matrix |
| strings of length avoiding patterns | automaton (KMP / Aho–Corasick) transition counts |
| tilings of a board | profile-to-profile transfer matrix (Profile DP) |
| Markov chain after steps | transition matrix (probabilities mod or doubles) |
Proof: matrix powers count walks
By induction on : = number of walks of length from to . For it’s the adjacency matrix. A walk of length from to is a walk of length to some followed by an edge , so the count is . Any linear DP is a weighted walk count in its state graph, and in a semiring the same argument holds with in place of , which is exactly why powers give shortest walks with exactly edges. Associativity of the semiring matrix product justifies fast exponentiation.
Sums of powers and many queries
- : exponentiate the block matrix , whose -th power has in the top-right block. Or divide and conquer: .
- Many queries : precompute once (), then each query multiplies the vector by matrices: per query instead of .
- Min-plus powers with “at most edges”: add a self-loop of weight 0 at every vertex.
- Large but a linear recurrence: use Kitamasa () or Bostan–Mori (); if only the sequence is known, recover the recurrence with Berlekamp–Massey from terms. Concretely, for , : matrix exponentiation costs operations, Kitamasa — a real difference once is in the hundreds.
- with special structure: circulant matrices multiply as cyclic convolutions ( or per product); upper-triangular with equal diagonals is a polynomial in a shift.
Proof: the block matrix gives the geometric sum
Let . Claim with . True for (). Then has top-left block and top-right block , while the bottom row stays . So the claim holds by induction.
Pitfalls
- The state must be closed under the transition: every quantity used on the right-hand side must be in the vector.
- Check that does not depend on ; if it depends on use the period product; if it depends on polynomially, you usually need extra state for powers of .
- matrices need handling: skip infinite entries rather than adding them.
Doubling DP: when the transition isn’t linear in a fixed-size vector
Not every “huge , small extra parameter ” DP fits the fixed-matrix mould above. When a quantity over supports both “append one more element” and “combine two halves directly” without going through a transition matrix at all, exponentiation-by-doubling on itself can beat even Kitamasa.
Example: (the elementary symmetric polynomial of ) for every simultaneously, up to , in — a transition-matrix approach would need state and cost .
template <class Z>
vector<Z> elementarySymmetricUpTo(i64 n, int K) {
// f(n) = e_k over {0, 1, ..., n}; since 0 contributes nothing, this is e_k over {1..n}
vector<Z> inv(K + 2, Z(1));
for (int i = 1; i <= K + 1; i++) inv[i] = Z(1) / Z(i);
auto rec = [&](auto&& self, i64 m) -> vector<Z> {
vector<Z> ans(K + 1, Z(0));
if (m == 0) {
ans[0] = 1;
return ans;
}
if (m & 1) { // {0..m} = {0..m-1} + {m}
vector<Z> l = self(self, m - 1);
for (int i = 0; i <= K; i++) {
ans[i] = l[i];
if (i) ans[i] += l[i - 1] * Z(m);
}
return ans;
}
// {0..m} = {0..p} + {p + 1 .. 2p} with p = m / 2: elements p + j for j = 1..p
i64 p = m / 2;
vector<Z> l = self(self, p);
// r = e over {p + 1, ..., 2p} = sum_j e_j(1..p) * p^{i-j} * C(p - j, i - j)
vector<Z> r(K + 1, Z(0)), pw(K + 1);
pw[0] = 1;
for (int i = 1; i <= K; i++) pw[i] = pw[i - 1] * Z(p);
for (int j = 0; j <= K; j++) {
if (p - j < 0) break;
Z c = 1; // C(p - j, t) built incrementally in t
for (int t = 0; j + t <= K; t++) {
if (p - j - t < 0) break;
r[j + t] += l[j] * pw[t] * c;
c *= Z(p - j - t);
c *= inv[t + 1];
}
}
for (int i = 0; i <= K; i++) {
for (int j = 0; i + j <= K; j++) ans[i + j] += l[i] * r[j];
}
return ans;
};
return rec(rec, n);
}Proof: the odd and even steps of the doubling recurrence
Work with generating functions: , since choosing the term from exactly of the factors and from the rest sums every -subset product exactly once.
Odd step (): , so — exactlyans[i] = l[i] + m*l[i-1].
Even step (, splitting into and ): the second half is the first half’s generating function with every element shifted by . For a shift by a constant applied to values with elementary symmetric polynomials , write , so where . Substituting gives , and extracting : — exactly the code’sr[j+t] += l[j] * pw[t] * C(p-j, t)with , . The final combinationans[i+j] += l[i]*r[j]is just multiplying the two halves’ generating functions.Tested against a direct DP (append elements one at a time, truncating at degree ) for random , .
This is the general pattern for a DP over a huge range that supports both “append one element” () and “double the range” () — not every such DP needs a transition matrix. The here are, up to reindexing, unsigned Stirling numbers of the first kind; for close to the direct or polynomial methods (Stirling Numbers) are the better choice instead.