, 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.

MethodCostField 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 termsfinds the minimal polynomial w.h.p. (Wiedemann)
UseHow
for huge and – in (or with NTT), then in : beats
as a polynomial invertible: ; in general interpolate values
inverseFaddeev–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;
}