Matrix arithmetic for contests: a fast multiplication loop, binary exponentiation, many power queries, geometric sums, semiring products (, , boolean), and Strassen. How to build the transition matrix of a DP is in Matrix Exponentiation DP; recurrences of order in are in Linear Recurrences. mint is from Modular Arithmetic.

for of size and of size . The product is associative but not commutative; , , .

TaskMethodCostNote
binary exponentiationthis note
for many queriesprecomputed , vector productsthis note
block matrix this note
solve , rank, inverseGaussian eliminationGaussian Elimination
determinant (field, composite modulus, exact integers)elimination, Euclid-style rows, BareissDeterminants
characteristic polynomialHessenberg reductionCharacteristic Polynomial
sparse system, sparse determinantWiedemann / LanczosSparse Matrices
spanning treesMatrix-TreeMatrix-Tree Theorem
perfect matching existsTutte matrix rank (random)General Matching
verify Freivalds per roundHashing and Verification
boolean productbitset rowsBitset

Everything works over with division by inverses; over a composite modulus division may fail, so use Euclid-style row reduction (repeatedly subtract multiples, as in the gcd) instead of pivoting by inverses.

using Mat = vector<vector<mint>>;
Mat eye(int n) { Mat r(n, vector<mint>(n, 0)); for (int i = 0; i < n; i++) r[i][i] = 1; return r; }
Mat operator*(const Mat& a, const Mat& b) {                // i, k, j order: rows of b and c are walked contiguously
    int n = sz(a), K = sz(b), m = sz(b[0]); Mat c(n, vector<mint>(m, 0));
    for (int i = 0; i < n; i++) for (int k = 0; k < K; k++) {
        if (a[i][k] == mint(0)) continue;
        mint x = a[i][k]; for (int j = 0; j < m; j++) c[i][j] += x * b[k][j];
    }
    return c;
}
Mat matPow(Mat a, ll e) { Mat r = eye(sz(a)); for (; e; e >>= 1, a = a * a) if (e & 1) r = r * a; return r; }
vector<mint> mulVec(const Mat& a, const vector<mint>& v) {
    vector<mint> r(sz(a), 0); for (int i = 0; i < sz(a); i++) for (int j = 0; j < sz(v); j++) r[i] += a[i][j] * v[j]; return r;
}
 
struct PowerQueries {                                      // A^e v in O(n^2 log e) per query after O(n^3 log E) setup
    vector<Mat> p;
    PowerQueries(const Mat& a, int LOG = 60) : p(LOG) { p[0] = a; for (int j = 1; j < LOG; j++) p[j] = p[j - 1] * p[j - 1]; }
    vector<mint> query(vector<mint> v, ull e) const { for (int j = 0; e; j++, e >>= 1) if (e & 1) v = mulVec(p[j], v); return v; }
};
 
Mat geometricSum(const Mat& a, ll e) {                     // sum_{i < e} a^i
    int n = sz(a); Mat b(2 * n, vector<mint>(2 * n, 0));
    for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) b[i][j] = a[i][j]; b[i][n + i] = 1; b[n + i][n + i] = 1; }
    Mat r = matPow(b, e), s(n, vector<mint>(n));
    for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) s[i][j] = r[i][n + j];
    return s;
}
 
vector<vector<ll>> mulFast(const vector<vector<ll>>& a, const vector<vector<ll>>& b, ll mod) {   // entries < mod < 2^30: reduce every 16 terms
    int n = sz(a), K = sz(b), m = sz(b[0]); vector<vector<ll>> c(n, vector<ll>(m));
    vector<ull> acc(m);
    for (int i = 0; i < n; i++) {
        fill(all(acc), 0);
        for (int k = 0; k < K; k++) {
            ull x = a[i][k]; for (int j = 0; j < m; j++) acc[j] += x * b[k][j];
            if ((k & 15) == 15) for (int j = 0; j < m; j++) acc[j] %= mod;
        }
        for (int j = 0; j < m; j++) c[i][j] = acc[j] % mod;
    }
    return c;
}

Semirings

Matrix multiplication and fast exponentiation work over any semiring : associative and commutative with identity , associative with identity , distributing over , and absorbing. then aggregates (with ) the -products of edge weights over all walks of exactly edges.

Semiring , computes
0, 1number (or total weight) of walks of length
, 0shortest walk with exactly edges
, 0longest walk with exactly edges
false, truereachability in exactly steps
, widest (bottleneck) walk
0, 1parity of the number of walks

“At most edges” with : add a 0-weight self-loop at every vertex. Max-plus: negate the weights. Cheapest route using exactly flights for is with this, a problem with no other reasonable approach. In there is no subtraction, so no Strassen and no algorithm is known (the APSP conjecture, Theoretical Algorithms).

struct MinPlusMatrix {
    static constexpr ll INF = numeric_limits<ll>::max() / 4;
    int n; vector<vector<ll>> d;
    explicit MinPlusMatrix(int size) : n(size), d(size, vector<ll>(size, INF)) {}
    static MinPlusMatrix identity(int size) { MinPlusMatrix r(size); for (int i = 0; i < size; i++) r.d[i][i] = 0; return r; }
    friend MinPlusMatrix operator*(const MinPlusMatrix& a, const MinPlusMatrix& b) {
        MinPlusMatrix c(a.n);
        for (int i = 0; i < a.n; i++) for (int k = 0; k < a.n; k++) {
            if (a.d[i][k] >= INF) continue;
            for (int j = 0; j < a.n; j++) if (b.d[k][j] < INF) c.d[i][j] = min(c.d[i][j], a.d[i][k] + b.d[k][j]);
        }
        return c;
    }
    MinPlusMatrix pow(ll k) const { MinPlusMatrix res = identity(n), b = *this; for (; k > 0; k >>= 1, b = b * b) if (k & 1) res = res * b; return res; }
};
 
template <class T, class Add, class Mul>                   // generic semiring product (zero = identity of add)
vector<vector<T>> semiringMul(const vector<vector<T>>& a, const vector<vector<T>>& b, T zero, Add add, Mul mul) {
    int n = sz(a), K = sz(b), m = sz(b[0]); vector<vector<T>> c(n, vector<T>(m, zero));
    for (int i = 0; i < n; i++) for (int k = 0; k < K; k++) for (int j = 0; j < m; j++) c[i][j] = add(c[i][j], mul(a[i][k], b[k][j]));
    return c;
}
 
ll graphPathsI(int n, const vector<pair<int, int>>& edges, ll k) {   // CSES 1723: walks 1 -> n with exactly k edges, mod p
    Mat a(n, vector<mint>(n, 0)); for (auto [u, v] : edges) a[u - 1][v - 1] += 1;
    return matPow(a, k)[0][n - 1].v;
}
ll graphPathsII(int n, const vector<array<ll, 3>>& edges, ll k) {   // CSES 1724: min weight walk 1 -> n with exactly k edges, -1 if none
    MinPlusMatrix a(n); for (auto [u, v, w] : edges) a.d[u - 1][v - 1] = min(a.d[u - 1][v - 1], w);
    ll r = a.pow(k).d[0][n - 1];
    return r >= MinPlusMatrix::INF ? -1 : r;
}

Graph Paths I and II (CSES). The DP = ways (resp. minimum cost) to be at after exactly edges has the same transition at every step, (resp. ), and goes up to : the state is the vector , the transition is the adjacency matrix in the (resp. ) semiring, and the answer is entry of its -th power. On the samples (edges , ) the answers are 2 walks and minimum cost 27 (weights 5, 4, 1, 2).

Fast matrix multiplication

AlgorithmExponentIn practice
schoolbookthe default; tuned loops reach several GFLOP/s
Strassen (1969)crossover in the hundreds; numerically less stable for doubles; fine modulo
Coppersmith–Winograd and successors (laser method)galactic
boolean, word-RAM with bitsetsbest for every practical boolean size
lower bound (reading the input) is open

Strassen computes a block product with 7 multiplications instead of 8 (the “trade multiplications for additions” idea of Karatsuba):
, , , , , , ;
, , , .

Mat strassen(const Mat& A, const Mat& B) {                 // square, size a power of two
    int n = sz(A);
    if (n <= 64) return A * B;
    int h = n / 2;
    auto blk = [&](const Mat& X, int r, int c) { Mat Y(h, vector<mint>(h)); for (int i = 0; i < h; i++) for (int j = 0; j < h; j++) Y[i][j] = X[r + i][c + j]; return Y; };
    auto add = [&](const Mat& X, const Mat& Y, int s) { Mat Z(h, vector<mint>(h)); for (int i = 0; i < h; i++) for (int j = 0; j < h; j++) Z[i][j] = s > 0 ? X[i][j] + Y[i][j] : X[i][j] - Y[i][j]; return Z; };
    Mat a11 = blk(A, 0, 0), a12 = blk(A, 0, h), a21 = blk(A, h, 0), a22 = blk(A, h, h);
    Mat b11 = blk(B, 0, 0), b12 = blk(B, 0, h), b21 = blk(B, h, 0), b22 = blk(B, h, h);
    Mat m1 = strassen(add(a11, a22, 1), add(b11, b22, 1)), m2 = strassen(add(a21, a22, 1), b11), m3 = strassen(a11, add(b12, b22, -1));
    Mat m4 = strassen(a22, add(b21, b11, -1)), m5 = strassen(add(a11, a12, 1), b22), m6 = strassen(add(a21, a11, -1), add(b11, b12, 1));
    Mat m7 = strassen(add(a12, a22, -1), add(b21, b22, 1));
    Mat C(n, vector<mint>(n));
    for (int i = 0; i < h; i++) for (int j = 0; j < h; j++) {
        C[i][j] = m1[i][j] + m4[i][j] - m5[i][j] + m7[i][j]; C[i][j + h] = m3[i][j] + m5[i][j];
        C[i + h][j] = m2[i][j] + m4[i][j]; C[i + h][j + h] = m1[i][j] - m2[i][j] + m3[i][j] + m6[i][j];
    }
    return C;
}