线性基
通过有限的线性基, 可以描述无限的线性空间.
算法竞赛中常用的线性基有两种, 实线性空间中的实数线性基和布尔域线性空间中的异或线性基. (布尔域线性空间中加法为异或, 数乘为与).
以异或线性基为例, 我们可以根据一组布尔序列构造出一组异或线性基. 这组基有如下性质:
- 任意非空子集的异或和不为0.
- 对中的任意元素, 都可以在中取出若干元素使其异或和为.
- 是满足性质1和2的极小向量组.
:::[异或线性基的典型用法]
- 判断一个数是否能表示成某数集子集的异或和;
- 求一个数表示成某数集子集异或和的方案;
- 求某数集的最大/最小/第k大/第k小子集异或和;
- 求某数在某数集子集异或和中的排名. :::
矩阵快速幂/矩阵加速递推
矩阵加速递推可以把线性递推式由优化到. 其中k为递推式的阶数.
如果一个递推式是线性的, 我们可以把它写成矩阵的形式, 一次递推即相当于左乘一次转移矩阵. 我们需要n次递推, 由于矩阵有结合律, 这相当于我们左乘转移矩阵的n次幂. 我们可以先使用快速幂算法求出矩阵的n次幂, 再用初始向量左乘得到结果.
以计算斐波那契数列第n项为例.
- 构造状态向量. . 我们知道他的初始态有两项. 这暗示我们状态有两项:
- 构建转移矩阵:
于是转移矩阵:
转移可以写成:
- 做快速幂即可.
// 列向量template <class S, int N>struct ColVector { array<S, N> a; ColVector(S def = S()) { fill(a.begin(), a.end(), def); }
S& operator[](int i) { return a[i]; } const S& operator[](int i) const { return a[i]; }};
// 矩阵template <typename S, int N>struct Matrix { array<array<S, N>, N> a; Matrix(S def = S()) { for (int i = 0; i < N; i++) { fill(a[i].begin(), a[i].end(), def); } }
S* operator[](int i) { return a[i].data(); } const S* operator[](int i) const { return a[i].data(); }
// 单位矩阵 static Matrix e() { Matrix ret; for (int i = 0; i < N; i++) { ret[i][i] = 1; } return ret; }
// 加法 friend Matrix operator+(Matrix lt, const Matrix& rt) { for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { lt[i][j] += rt[i][j]; } } return lt; }
// 乘法 friend Matrix operator*(const Matrix& lt, const Matrix& rt) { Matrix ret; for (int i = 0; i < N; i++) { for (int k = 0; k < N; k++) { if (lt[i][k] == S()) { continue; }
for (int j = 0; j < N; j++) { ret[i][j] += lt[i][k] * rt[k][j]; } } } return ret; }
Matrix pow(ll b) const { Matrix ret = e(); Matrix t = *this; while (b > 0) { if (b & 1) { ret = ret * t; }
t = t * t; b >>= 1; } return ret; }
ColVector<S, N> operator*(const ColVector<S, N>& vec) { ColVector<S, N> ret; for (int i = 0; i < N; i++) { for (int k = 0; k < N; k++) { if (a[i][k] == S()) { continue; }
ret[i] = ret[i] + (a[i][k] * vec[k]); } } return ret; }};矩阵快速幂还有其他trick, 这里简单介绍一些.
邻接矩阵的快速幂
以下邻接矩阵的定义: adj[v][u]表示从u到v的边, 请注意这和常见的邻接矩阵是反过来的(方便左乘).
首先考虑这样一个问题: 对于给定的简单图, 顶点数n <= 100, 计算由k <= 1e9步组成的路径数.
我们可以很容易想到这样一个递推方法, 设dp[i][j]表示以节点i为结尾的, 由j步组成的路径数, 设c[j][i]为i到j是否有边的指示器(有边为1, 无边为0), 则:
dp[1][j] = c[1][1] * dp[1][j - 1] + c[1][2] * dp[2][j - 1] + ... + c[1][n] * dp[n][j - 1].dp[2][j] = c[2][1] * dp[1][j - 1] + ...- …
dp[n][j] = c[n][1] * dp[1][n - 1] + ...
我们可以发现这可以写成矩阵形式. 状态向量即为DP = dp[1][j], dp[2][j], ..., dp[n][j], 转移矩阵为c[i][j]组成的矩阵, 我们会发现这个矩阵正是图的邻接矩阵. 所以使用邻接矩阵建图, 再对邻接矩阵做快速幂即可.
(min, +)矩阵乘法(新定义)
回想floyd算法的过程. dp[i][j] = min(dp[i][t] + dp[t][j]), 我们会发现dp[i][t]的第二维和dp[t][j]的第一维是相同的, 而且每次t需要遍历所有点, 这恰好和矩阵乘法有相似之处(矩阵乘法: dp[i][j] = dp[i][1] * dp[1][j] + dp[i][2] * dp[2][j] + ...), 也就是说原来的乘法相当于新的加法, 原来的加法变成了新的min操作, 可以证明这样的矩阵乘法是满足结合律的, 所以可以使用快速幂优化. 这样的矩阵乘法也可以叫最短路径矩阵乘法, 因为常用来求拥有k条边的最短路径问题.
例题: CF102644F
代码:
template <int N>struct Matrix_mp { array<array<ll, N>, N> a; Matrix_mp() { for (int i = 0; i < N; i++) { fill(a[i].begin(), a[i].end(), INFLL); } }
ll* operator[](int i) { return a[i].data(); } const ll* operator[](int i) const { return a[i].data(); }
static Matrix_mp e() { Matrix_mp ret; for (int i = 0; i < N; i++) { ret[i][i] = 0; } return ret; }
friend Matrix_mp operator+(Matrix_mp lt, const Matrix_mp& rt) { for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { lt[i][j] = min(lt[i][j], rt[i][j]); } } return lt; } friend Matrix_mp operator*(const Matrix_mp& lt, const Matrix_mp& rt) { Matrix_mp ret; for (int i = 0; i < N; i++) { for (int k = 0; k < N; k++) { if (lt[i][k] >= INFLL) { continue; }
for (int j = 0; j < N; j++) { ll now; if (rt[k][j] >= INFLL) { now = INFLL; } else { now = lt[i][k] + rt[k][j]; } ret[i][j] = min(ret[i][j], now); } } } return ret; }
Matrix_mp pow(ll b) const { Matrix_mp ret = e(); Matrix_mp t = *this; while (b > 0) { if (b & 1) { ret = ret * t; }
t = t * t; b >>= 1; } return ret; }
ColVector<ll, N> operator*(const ColVector<ll, N>& vec) { ColVector<ll, N> ret(INFLL); for (int i = 0; i < N; i++) { for (int k = 0; k < N; k++) { if (a[i][k] >= INFLL) { continue; } ll now; if (a[i][k] >= INFLL || vec[k] >= INFLL) { now = INFLL; } else { now = a[i][k] + vec[k]; } ret[i] = min(ret[i], now); } } return ret; }};