, with and ; its roots are the eigenvalues, so (closed walks of length in a graph). Cayley–Hamilton: , so every power is a polynomial of degree in : this is the bridge from matrix powers to linear recurrences (any coordinate of satisfies the recurrence with characteristic polynomial ). mint from Modular Arithmetic.
Frobenius (rational canonical) form is the formal name for this fact as a change of basis: is similar to a block-diagonal matrix of companion matrices, one per invariant factor of the minimal polynomial (a single block, the companion matrix of itself, whenever the minimal and characteristic polynomials agree — the generic case for a random matrix). A companion matrix’s powers acting on are exactly the coefficient-shift recurrence above, so computing via “reduce then apply to ” (Kitamasa / Bostan–Mori) is fast exponentiation in the Frobenius form, without ever constructing the change-of-basis matrix explicitly.
| Method | Cost | Field requirement |
|---|---|---|
| Hessenberg reduction + recurrence | ( in about 0.2 s) | any field |
| Faddeev–LeVerrier | (matrix products and traces) | divides by : characteristic 0 or |
| interpolation of at points | ||
| Berlekamp–Massey on (random ) | for terms | finds the minimal polynomial w.h.p. (Wiedemann) |
| Use | How |
|---|---|
| for huge and – | in (or with NTT), then in : beats |
| as a polynomial | invertible: ; in general interpolate values |
| inverse | Faddeev–LeVerrier byproduct |
| closed walks of each length | = Newton power sums of the roots |
template <class T> vector<T> characteristicPolynomial(vector<vector<T>> a) { // p[0..n], p[n] = 1, p[0] = (-1)^n det A
int n = sz(a);
for (int j = 0; j + 2 < n; j++) { // similarity transforms to upper Hessenberg form
int piv = j + 1; while (piv < n && a[piv][j] == T(0)) piv++;
if (piv == n) continue;
if (piv != j + 1) { swap(a[piv], a[j + 1]); for (int i = 0; i < n; i++) swap(a[i][piv], a[i][j + 1]); }
T inv = T(1) / a[j + 1][j];
for (int i = j + 2; i < n; i++) {
T c = a[i][j] * inv; if (c == T(0)) continue;
for (int k = j; k < n; k++) a[i][k] -= c * a[j + 1][k]; // row i -= c row j+1
for (int k = 0; k < n; k++) a[k][j + 1] += c * a[k][i]; // column j+1 += c column i
}
}
vector<vector<T>> p(n + 1); p[0] = {T(1)};
for (int i = 0; i < n; i++) { // p_{i+1} = (x - h_ii) p_i - sum_t h_ti (h_{t+1,t}..h_{i,i-1}) p_t
p[i + 1].assign(i + 2, T(0));
for (int k = 0; k <= i; k++) p[i + 1][k + 1] += p[i][k], p[i + 1][k] -= a[i][i] * p[i][k];
T prod = 1;
for (int t = i - 1; t >= 0; t--) {
prod *= a[t + 1][t]; if (prod == T(0)) break;
T c = prod * a[t][i]; for (int k = 0; k <= t; k++) p[i + 1][k] -= c * p[t][k];
}
}
return p[n];
}
template <class T> vector<T> faddeevLeVerrier(const vector<vector<T>>& A, vector<vector<T>>* inverse = nullptr) { // c[0..n] highest first: [1, c_{n-1}, ..., c_0]
int n = sz(A); vector<T> c(n + 1, T(0)); c[0] = T(1);
vector<vector<T>> M(n, vector<T>(n, T(0))), prev; for (int i = 0; i < n; i++) M[i][i] = T(1);
for (int k = 1; k <= n; k++) {
vector<vector<T>> AM(n, vector<T>(n, T(0)));
for (int i = 0; i < n; i++) for (int l = 0; l < n; l++) for (int j = 0; j < n; j++) AM[i][j] += A[i][l] * M[l][j];
T tr = T(0); for (int i = 0; i < n; i++) tr += AM[i][i];
c[k] = -tr / T(k);
prev = M; M = AM; for (int i = 0; i < n; i++) M[i][i] += c[k]; // M_k = A M_{k-1} + c I; M_n = 0 (Cayley-Hamilton)
}
if (inverse && n && c[n] != T(0)) { *inverse = prev; for (auto& r : *inverse) for (auto& x : r) x = -x / c[n]; }
return c;
}
vector<mint> powerTimesVector(const vector<vector<mint>>& A, vector<mint> v, ll k) { // A^k v in O(n^2 log k + n^3)
int n = sz(A); if (n == 0) return v;
vector<mint> chi = characteristicPolynomial(A); // monic, degree n
auto mulMod = [&](const vector<mint>& a, const vector<mint>& b) { // a * b mod chi
vector<mint> r(2 * n, 0);
for (int i = 0; i < n; i++) if (a[i] != mint(0)) for (int j = 0; j < n; j++) r[i + j] += a[i] * b[j];
for (int d = 2 * n - 1; d >= n; d--) if (r[d] != mint(0)) { mint f = r[d]; for (int t = 0; t <= n; t++) r[d - n + t] -= f * chi[t]; }
r.resize(n); return r;
};
vector<mint> res(n, 0), base(n, 0); res[0] = 1;
if (n == 1) base[0] = -chi[0]; else base[1] = 1; // x mod chi
for (; k; k >>= 1, base = mulMod(base, base)) if (k & 1) res = mulMod(res, base);
vector<mint> out(n, 0), cur = v; // sum_i res_i A^i v
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) out[j] += res[i] * cur[j];
vector<mint> nxt(n, 0); for (int r = 0; r < n; r++) for (int c = 0; c < n; c++) nxt[r] += A[r][c] * cur[c];
cur = nxt;
}
return out;
}
vector<mint> detAffine(const vector<vector<mint>>& A, const vector<vector<mint>>& B) { // coefficients of det(A + xB), O(n^4), p > n
int n = sz(A); vector<mint> ys(n + 1), res(n + 1, 0);
auto det = [&](vector<vector<mint>> a) {
mint d = 1;
for (int i = 0; i < n; i++) {
int p = -1; for (int r = i; r < n; r++) if (a[r][i] != mint(0)) { p = r; break; }
if (p < 0) return mint(0);
if (p != i) swap(a[p], a[i]), d = -d;
d *= a[i][i]; mint inv = a[i][i].inv();
for (int r = i + 1; r < n; r++) { mint f = a[r][i] * inv; for (int c = i; c < n; c++) a[r][c] -= f * a[i][c]; }
}
return d;
};
for (int x = 0; x <= n; x++) { auto M = A; for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) M[i][j] += mint(x) * B[i][j]; ys[x] = det(M); }
for (int i = 0; i <= n; i++) { // Lagrange: prod_{j != i} (x - j) / (i - j)
vector<mint> poly = {1}; mint denom = 1;
for (int j = 0; j <= n; j++) if (j != i) {
vector<mint> q(sz(poly) + 1, 0);
for (int t = 0; t < sz(poly); t++) q[t + 1] += poly[t], q[t] -= mint(j) * poly[t];
poly = q; denom *= mint(i - j);
}
mint f = ys[i] / denom; for (int t = 0; t <= n; t++) res[t] += f * poly[t];
}
return res;
}Proof: Hessenberg reduction and its recurrence
Similarity. Row row is left multiplication by , and column column is right multiplication by ; swapping rows and the same columns is . Similar matrices have the same characteristic polynomial: . Choosing clears for ; the column operation only touches column , so earlier cleared columns stay zero, and the result is upper Hessenberg (zero below the first subdiagonal). Recurrence. Let be the leading block and . Expand along its last column, whose entries are and (). Deleting row and the last column leaves a block-triangular matrix with blocks and an upper-triangular block whose diagonal is . With the cofactor sign the term is , which is the code’s update. Total cost for the reduction and for the recurrence.
Proof: Cayley–Hamilton, Faddeev–LeVerrier, powers and det(A + xB)
Cayley–Hamilton. , and has matrix coefficients. Comparing coefficients of in and multiplying the equation by on the right, the left sides telescope to 0, so . Faddeev–LeVerrier. Define , , so and . Newton’s identities for the eigenvalues give , which is the update. Then (a free correctness check), so and . Dividing by requires characteristic 0 or ; modulo a small prime use the Hessenberg method. ( and the traces of all powers appear along the way.)
Powers. Write with ; substituting and using gives , and is computed by binary exponentiation in the ring . is a polynomial of degree in (Leibniz), so values determine it (Lagrange interpolation needs the points distinct mod ); if is invertible, . All functions were checked against brute-force determinants of evaluated at many points, direct matrix powers and .
Related notes
- Linear Recurrences (Kitamasa, Bostan–Mori, Berlekamp–Massey)
- Sparse and Structured Matrices (Wiedemann: minimal polynomial of sparse matrices)
- Determinants (Bareiss for exact values)