矩阵

在 GF(2) 中提取 Krylov 状态序列的最小递推,用多项式快速幂求 A^k b。

OJ: shumeng

题目 ID: CSP201512E

难度:省选/NOI-

标签:线性代数GF(2)矩阵快速幂

日期: 2026-07-31 16:21

形式化题目

在 GF(2) 上(加法为异或、乘法为与)给定一个可逆的 m×mm \times m 矩阵 AA 和初始向量 bb。回答 nn 组询问,每组给定非负整数 kk,输出 AkbA^k b(其中 A0b=bA^0 b = b)。

思路

直接对每个询问做矩阵快速幂需要 O(m3logk)O(m^3 \log k),而 mm 最大 10001000,不可接受。本题的关键是:只针对给定的一个向量 bb 建立 Krylov 序列,而不是对整张矩阵做幂。

Krylov 序列与最小递推

考虑向量序列:

b,  Ab,  A2b,  b,\; Ab,\; A^2b,\; \ldots

这些向量都在 mm 维空间中,最多 mm 个线性无关,因此必存在最小的正整数 dd 使得 AdbA^d b 可被 A0b,,Ad1bA^0b, \ldots, A^{d-1}b 线性表出:

Adb=i=0d1ciAibA^d b = \sum_{i=0}^{d-1} c_i A^i b

这个线性关系对应一个递推多项式 P(x)=xdcixiP(x) = x^d - \sum c_i x^i,满足 P(A)b=0P(A)b = 0

高斯消元找递推

逐项生成状态 state[i]=Aibstate[i] = A^i b,对每个新向量做“按最高位”的位集高斯消元:

  • 若线性无关,加入基,记录它对应的 AA 的幂次表达式;
  • 若消到零,说明找到了首个线性关系,得到 dd 和系数 cic_i,停止。

多项式快速幂

对每个询问,求 xkmodP(x)x^k \bmod P(x) 的多项式系数(在 GF(2) 上做多项式乘法并对 P(x)P(x) 取模),答案就是:

Akb=i[xi](xkmodP(x))AibA^k b = \sum_i [x^i]\big(x^k \bmod P(x)\big) \cdot A^i b

位集让每个多项式乘法只需 O(d2/64)O(d^2/64) 的时间。

代码

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-07-31 16:21
 * update_at: 2026-08-17 23:01
 */
#include <bits/stdc++.h>
using namespace std;

const int MAXM = 1005; // 向量/矩阵最大维数
const int MAXP = 2010; // 递推多项式最高次数(2*m 量级)

int m;
bitset<MAXM> matrix_row[MAXM];    // 矩阵 A 的每一行,用于把向量左乘 A
bitset<MAXM> linear_basis[MAXM];  // 高斯消元后的向量基(按最高位归位)
bitset<MAXM> linear_expression[MAXM]; // 基向量对应的多项式系数(列)
bitset<MAXM> state[MAXM];         // state[i] = A^i * b
bitset<MAXP> relation;            // 最小递推多项式 P(x),满足 x^d = sum relation[i]*x^i

// 计算 A * vector_value(GF(2) 下,行向量点积取异或)。
bitset<MAXM> multiply_vector(const bitset<MAXM> &vector_value) {
    bitset<MAXM> result;
    for (int i = 0; i < m; i++) {
        result[i] = (matrix_row[i] & vector_value).count() % 2;
    }
    return result;
}

// GF(2) 多项式乘法并对 P(x) 取模(次数为 degree)。
bitset<MAXP> multiply_polynomial(const bitset<MAXP> &left, const bitset<MAXP> &right,
                                 int degree) {
    bitset<MAXP> result;
    // 先做普通多项式乘法,再对次数 >= degree 的项用 x^degree = P(x) 归约。
    for (int i = 0; i < degree; i++) {
        if (left[i]) result ^= right << i;
    }
    for (int i = 2 * degree - 2; i >= degree; i--) {
        if (result[i]) result ^= relation << (i - degree);
    }
    return result;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> m;
    for (int i = 0; i < m; i++) {
        string line;
        cin >> line;
        for (int j = 0; j < m; j++) matrix_row[i][j] = line[j] == '1';
    }
    string initial;
    cin >> initial;
    bitset<MAXM> current; // 当前状态向量 b
    for (int i = 0; i < m; i++) current[i] = initial[i] == '1';

    // 逐项生成 Krylov 序列 b, Ab, A^2b, ...,用高斯消元找第一个线性相关。
    int degree = 0;
    for (int step = 0; step <= m; step++) {
        bitset<MAXM> value = current;
        bitset<MAXM> expression;
        expression[step] = 1; // 初始表示 A^step * b = 1 * (A^step * b)
        // 按最高位从高到低消元。
        for (int bit = m - 1; bit >= 0; bit--) {
            if (!value[bit]) continue;
            if (linear_basis[bit].none()) {
                // 当前向量线性无关,加入基并记录它对应的 A 的幂次。
                linear_basis[bit] = value;
                linear_expression[bit] = expression;
                state[step] = current;
                degree = step + 1;
                break;
            }
            value ^= linear_basis[bit];
            expression ^= linear_expression[bit];
        }
        if (value.none()) {
            // 找到首个线性关系:A^step * b = sum expression[i] * A^i * b。
            degree = step;
            for (int i = 0; i < degree; i++) relation[i] = expression[i];
            relation[degree] = 1; // P(x) = x^degree - sum expression[i]*x^i
            break;
        }
        current = multiply_vector(current);
    }

    int q;
    cin >> q;
    while (q--) {
        long long k;
        cin >> k;
        // 用多项式快速幂计算 x^k mod P(x),结果系数决定 state 的线性组合。
        bitset<MAXP> result, power;
        result[0] = 1;
        if (degree == 1) power[0] = relation[0]; // 一次递推的特例:x = relation[0]
        else power[1] = 1;                        // 否则 x^1
        while (k > 0) {
            if (k & 1) result = multiply_polynomial(result, power, degree);
            power = multiply_polynomial(power, power, degree);
            k >>= 1;
        }
        // 答案 = sum result[i] * state[i]。
        bitset<MAXM> answer;
        for (int i = 0; i < degree; i++) {
            if (result[i]) answer ^= state[i];
        }
        for (int i = 0; i < m; i++) cout << answer[i];
        cout << '\n';
    }
    return 0;
}

复杂度

dd 为递推阶数,dmd \leqslant m

  • 预处理:生成 mm 个状态并消元,位集操作下约为 O(m3/64)O(m^3/64)
  • 每次询问:多项式快速幂,O(d2logk/64)O(d^2 \log k / 64)

总结

不需要显式计算 m×mm \times m 矩阵的幂。只针对给定初始向量建立 Krylov 序列,维数最多为 mm,用位集高斯消元提取最小递推多项式后,任意高次幂都能通过多项式取模快速求出。这是“线性递推 + 快速幂”思想在向量空间中的推广。