线性代数与高斯消元 (Linear Algebra & Gaussian Elimination)¶
线性代数(Linear Algebra) 在算法竞赛中是处理线性递推加速、多元一次方程组求解、概率期望图论建模以及异或空间基底的数学核心基石。
1. 矩阵运算与矩阵快速幂¶
1.1 矩阵乘法¶
设矩阵 \(A\) 的大小为 \(m \times k\),矩阵 \(B\) 的大小为 \(k \times n\),则它们的乘积矩阵 \(C = A \times B\) 的大小为 \(m \times n\)。其中每个元素满足:
- 运算律:
- 满足结合律:\((A \times B) \times C = A \times (B \times C)\);
- 满足分配律:\(A \times (B + C) = A \times B + A \times C\);
- 绝不满足交换律:一般情况下 \(A \times B \ne B \times A\)。
1.2 矩阵快速幂加速线性递推¶
由于矩阵乘法满足结合律,我们可以利用二分快速幂在 \(\mathcal{O}(m^3 \log N)\) 的时间复杂度内计算方阵的 \(N\) 次幂 \(A^N\)(其中 \(m\) 为矩阵阶数)。
斐波那契数列极速递推:¶
斐波那契递推式为 \(F_n = F_{n-1} + F_{n-2}\)(\(F_1 = 1, F_2 = 1\))。
将其构造成一阶状态向量与转移矩阵的形式:
通过数学归纳法展开:
通过计算矩阵的 \(n-2\) 次幂,可将常数项线性递推的复杂度从 \(\mathcal{O}(N)\) 降至 \(\mathcal{O}(\log N)\)!
#include <vector>
using Matrix = std::vector<std::vector<long long>>;
constexpr long long MOD = 1e9 + 7;
Matrix multiply(const Matrix& A, const Matrix& B) {
int n = A.size();
int m = B[0].size();
int k = B.size();
Matrix C(n, std::vector<long long>(m, 0));
for (int i = 0; i < n; ++i) {
for (int p = 0; p < k; ++p) {
if (A[i][p] == 0) continue;
for (int j = 0; j < m; ++j) {
C[i][j] = (C[i][j] + A[i][p] * B[p][j]) % MOD;
}
}
}
return C;
}
Matrix matrix_power(Matrix A, long long exp) {
int n = A.size();
Matrix res(n, std::vector<long long>(n, 0));
for (int i = 0; i < n; ++i) res[i][i] = 1; // 单位矩阵
while (exp > 0) {
if (exp & 1) res = multiply(res, A);
A = multiply(A, A);
exp >>= 1;
}
return res;
}
2. 高斯消元法 (Gaussian Elimination)¶
高斯消元法通过对增广矩阵进行初等行变换,将一般的多元一次方程组化为上三角矩阵(或行简化阶梯形矩阵),从而确定解的存在性并求解。
2.1 解的三种可能状态判定¶
对于包含 \(n\) 个未知数、\(n\) 个方程的实数方程组: 1. 唯一解:消元后每行都有唯一的非零主元; 2. 无解:消元过程中出现行形如 \([0, 0, \dots, 0 \mid c]\)(其中 \(c \ne 0\)),即 \(0 = c\) 产生矛盾; 3. 无穷多解:消元后出现的有效非零行数 \(r < n\),且不存在矛盾行。
#include <vector>
#include <cmath>
#include <algorithm>
constexpr double EPS = 1e-8;
// 返回值: 0: 唯一解, 1: 无穷多解, -1: 无解
// a 为大小为 n * (n + 1) 的增广矩阵,解保存在 ans 中
int gauss(std::vector<std::vector<double>>& a, int n, std::vector<double>& ans) {
int row = 0;
for (int col = 0; col < n; ++col) {
// 1. 列主元选择: 选取当前列绝对值最大的行交换到当前行 (提高数值稳定性)
int pivot = row;
for (int i = row + 1; i < n; ++i) {
if (std::abs(a[i][col]) > std::abs(a[pivot][col])) {
pivot = i;
}
}
if (std::abs(a[pivot][col]) < EPS) continue; // 当前列全为 0,跳过
std::swap(a[row], a[pivot]);
// 2. 将主元归一化为 1
for (int j = n; j >= col; --j) {
a[row][j] /= a[row][col];
}
// 3. 消去其余所有行该列的系数 (高斯-约旦消元)
for (int i = 0; i < n; ++i) {
if (i != row && std::abs(a[i][col]) > EPS) {
double factor = a[i][col];
for (int j = col; j <= n; ++j) {
a[i][j] -= factor * a[row][j];
}
}
}
++row;
}
// 4. 检查解的合法性
if (row < n) {
for (int i = row; i < n; ++i) {
if (std::abs(a[i][n]) > EPS) return -1; // 出现 0 = c (无解)
}
return 1; // 无穷多解
}
ans.resize(n);
for (int i = 0; i < n; ++i) {
ans[i] = a[i][n];
}
return 0; // 唯一解
}
3. 线性基 (Linear Basis) 与 异或空间¶
在处理子集异或和问题时,线性基(Linear Basis) 是处理异或向量空间的专用数据结构。
3.1 核心性质¶
设原集合为 \(S = \{a_1, a_2, \dots, a_n\}\),其线性基为 \(B = \{b_0, b_1, \dots, b_{63}\}\): 1. 张成空间等价:\(B\) 中的元素通过异或能够表示出的所有数值集合,与原数组 \(S\) 所有子集异或和集合完全相同; 2. 线性无关性:\(B\) 中任意非空子集的异或和均不为 \(0\); 3. 极小性:若原数组最大数值在 64 位以内,线性基中最多只包含 \(64\) 个元素!
3.2 线性基标准模板¶
class LinearBasis {
private:
std::vector<long long> basis;
public:
LinearBasis() : basis(64, 0) {}
// 插入一个元素,返回是否成功插入(若线性相关则插入失败返回 false)
bool insert(long long x) {
for (int i = 63; i >= 0; --i) {
if ((x >> i) & 1) {
if (basis[i] == 0) {
basis[i] = x;
return true;
}
x ^= basis[i];
}
}
return false;
}
// 查询原集合所有子集异或和的最大值
long long query_max() {
long long res = 0;
for (int i = 63; i >= 0; --i) {
if ((res ^ basis[i]) > res) {
res ^= basis[i];
}
}
return res;
}
};
4. LeetCode 经典真题精选与深度精析¶
-
LeetCode 509 - 斐波那契数 (Fibonacci Number)
简单提示
矩阵快速幂基石题。
- 利用递推状态转移矩阵 \(\begin{bmatrix} 1 & 1 \\ 1 & 0 \end{bmatrix}\)。
- 矩阵快速幂求解可以在 \(\mathcal{O}(\log N)\) 时间内完成。
-
LeetCode 1137 - 第 N 个泰波那契数 (N-th Tribonacci Number)
简单提示
三阶线性递推矩阵快速幂。
- 递推式:\(T_n = T_{n-1} + T_{n-2} + T_{n-3}\)。
- 转移矩阵为 \(3 \times 3\):\(\begin{bmatrix} 1 & 1 & 1 \\ 1 & 0 & 0 \\ 0 & 1 & 0 \end{bmatrix}\)。
- 时间复杂度 \(\mathcal{O}(3^3 \log N) = \mathcal{O}(\log N)\)。
-
LeetCode 2851 - 字符串转换 (String Transformation)
困难提示
KMP 匹配计数 + 矩阵快速幂加速状态转移。
- 首先利用 KMP 算法求出字符串 \(s\) 在旋转后与 \(t\) 完全重合的旋转步数种数 \(c\)。
- 每次操作:在其余 \(n-1\) 个位置中随机选择一个旋转步数。
- 设 \(dp[k][0]\) 为 \(k\) 步后停留在匹配状态的方案数,\(dp[k][1]\) 为停留在不匹配状态的方案数。
- 建立 \(2 \times 2\) 状态转移矩阵,使用矩阵快速幂计算 \(k\) 次转移。
- 时间复杂度 \(\mathcal{O}(N + \log k)\)。