矩阵
本文介绍线性代数中一个非常重要的内容——矩阵(Matrix),主要讲解矩阵的性质、运算以及在常系数齐次递推式上的应用。
定义¶
对于矩阵
一般用
性质¶
矩阵的逆¶
逆矩阵可以用 高斯消元 的方式来求。
运算¶
矩阵的加减法是逐个元素进行的。
矩阵乘法¶
矩阵相乘只有在第一个矩阵的列数和第二个矩阵的行数相同时才有意义。
设
其中矩阵
如果没看懂上面的式子,没关系。通俗的讲,在矩阵乘法中,结果
矩阵乘法满足结合律,不满足一般的交换律。
利用结合律,矩阵乘法可以利用 快速幂 的思想来优化。
在比赛中,由于线性递推式可以表示成矩阵乘法的形式,也通常用矩阵快速幂来求线性递推数列的某一项。
优化¶
首先对于比较小的矩阵,可以考虑直接手动展开循环以减小常数。
可以重新排列循环以提高空间局部性,这样的优化不会改变矩阵乘法的时间复杂度,但是会在得到常数级别的提升。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | // 以下文的参考代码为例
inline mat operator*(const mat& T) const {
mat res;
for (int i = 0; i < sz; ++i)
for (int j = 0; j < sz; ++j)
for (int k = 0; k < sz; ++k) {
res.a[i][j] += mul(a[i][k], T.a[k][j]);
res.a[i][j] %= MOD;
}
return res;
}
// 不如
inline mat operator*(const mat& T) const {
mat res;
int r;
for (int i = 0; i < sz; ++i)
for (int k = 0; k < sz; ++k) {
r = a[i][k];
for (int j = 0; j < sz; ++j)
res.a[i][j] += T.a[k][j] * r, res.a[i][j] %= MOD;
}
return res;
}
|
参考代码¶
一般来说,可以用一个二维数组来模拟矩阵。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | struct mat {
LL a[sz][sz];
inline mat() { memset(a, 0, sizeof a); }
inline mat operator-(const mat& T) const {
mat res;
for (int i = 0; i < sz; ++i)
for (int j = 0; j < sz; ++j) {
res.a[i][j] = (a[i][j] - T.a[i][j]) % MOD;
}
return res;
}
inline mat operator+(const mat& T) const {
mat res;
for (int i = 0; i < sz; ++i)
for (int j = 0; j < sz; ++j) {
res.a[i][j] = (a[i][j] + T.a[i][j]) % MOD;
}
return res;
}
inline mat operator*(const mat& T) const {
mat res;
int r;
for (int i = 0; i < sz; ++i)
for (int k = 0; k < sz; ++k) {
r = a[i][k];
for (int j = 0; j < sz; ++j)
res.a[i][j] += T.a[k][j] * r, res.a[i][j] %= MOD;
}
return res;
}
inline mat operator^(LL x) const {
mat res, bas;
for (int i = 0; i < sz; ++i) res.a[i][i] = 1;
for (int i = 0; i < sz; ++i)
for (int j = 0; j < sz; ++j) bas.a[i][j] = a[i][j] % MOD;
while (x) {
if (x & 1) res = res * bas;
bas = bas * bas;
x >>= 1;
}
return res;
}
};
|
应用¶
矩阵加速递推¶
斐波那契数列(Fibonacci Sequence)大家应该都非常的熟悉了。在斐波那契数列当中,
如果有一道题目让你求斐波那契数列第
设
试推导一个矩阵
怎么推呢?因为
综上所述:
转化为代码,应该怎么求呢?
定义初始矩阵
注意,矩阵乘法不满足交换律,所以一定不能写成
为什么要乘上
下面是求斐波那契数列第
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | const int mod = 1000000007;
struct Matrix {
int a[3][3];
Matrix() { memset(a, 0, sizeof a); }
Matrix operator*(const Matrix &b) const {
Matrix res;
for (int i = 1; i <= 2; ++i)
for (int j = 1; j <= 2; ++j)
for (int k = 1; k <= 2; ++k)
res.a[i][j] = (res.a[i][j] + a[i][k] * b.a[k][j]) % mod;
return res;
}
} ans, base;
void init() {
base.a[1][1] = base.a[1][2] = base.a[2][1] = 1;
ans.a[1][1] = ans.a[1][2] = 1;
}
void qpow(int b) {
while (b) {
if (b & 1) ans = ans * base;
base = base * base;
b >>= 1;
}
}
int main() {
int n = read();
if (n <= 2) return puts("1"), 0;
init();
qpow(n - 2);
println(ans.a[1][1] % mod);
}
|
这是一个稍微复杂一些的例子。
我们发现,
但是发现如果矩阵仅有这三个元素
于是考虑构造一个更大的矩阵。
我们希望构造一个递推矩阵可以转移到
转移矩阵即为
矩阵表达修改¶
「THUSCH 2017」大魔法师
大魔法师小 L 制作了
我们用
小 L 计划施展
-
魔力激发:令区间里每个水晶球中 特定属性 的能量爆发,从而使另一个 特定属性 的能量增强。具体来说,有以下三种可能的表现形式:
- 火元素激发水元素能量:令
A_i = A_i + B_i - 土元素激发火元素能量:令
B_i = B_i + C_i -
水元素激发土元素能量:令
C_i = C_i + A_i 需要注意的是,增强一种属性的能量并不会改变另一种属性的能量,例如
A_i = A_i + B_i B_i
- 火元素激发水元素能量:令
-
魔力增强:小 L 挥舞法杖,消耗自身
v - 火元素能量定值增强:令
A_i = A_i + v - 水元素能量翻倍增强:令
B_i=B_i \cdot v - 土元素能量吸收融合:令
C_i = v
- 火元素能量定值增强:令
-
魔力释放:小 L 将区间里所有水晶球的能量聚集在一起,融合成一个新的水晶球,然后送给场外观众。生成的水晶球每种属性的能量值等于区间内所有水晶球对应能量值的代数和。需要注意的是,魔力释放的过程不会真正改变区间内水晶球的能量。
值得一提的是,小 L 制造和融合的水晶球的原材料都是定制版的 OI 工厂水晶,所以这些水晶球有一个能量阈值
小 W 为小 L(唯一的)观众,围观了整个表演,并且收到了小 L 在表演中融合的每个水晶球。小 W 想知道,这些水晶球蕴涵的三种属性的能量值分别是多少。
由于矩阵的结合律和分配律成立,单点修改可以自然地推广到区间,即推出矩阵后直接用线段树维护区间矩阵乘积即可。
下面将举几个例子。
「LibreOJ 6208」树上询问
有一棵
给出三种操作:
\operatorname{Add}( x , d ) x k_i\leftarrow k_i + d \operatorname{Mul}( x , d ) x t_i\leftarrow t_i + d \times k_i -
\operatorname{Query}( x ) x t_x n,~m \leq 100000, ~-10 \leq d \leq 10
若直接思考,下放操作和维护信息并不是很好想。但是矩阵可以轻松地表达。
定长路径统计¶
问题描述
给一个
我们将这个图用邻接矩阵
显然,该邻接矩阵对应
假设我们知道长度为
我们可以把它看作矩阵乘法的运算,于是上述转移可以描述为
那么把这个递推式展开可以得到
要计算这个矩阵幂,我们可以使用快速幂(二进制取幂)的思想,在
定长最短路¶
问题描述
给你一个
我们仍构造这个图的邻接矩阵
显然上述矩阵对应
事实上我们可以类比矩阵乘法,你发现上述转移只是把矩阵乘法的乘积求和变成相加取最小值,于是我们定义这个运算为
于是得到
展开递推式得到
我们仍然可以用矩阵快速幂的方法计算上式,因为它显然是具有结合律的。时间复杂度
限长路径计数/最短路¶
上述算法只适用于边数固定的情况。然而我们可以改进算法以解决边数小于等于
问题描述
给一个
我们简单修改一下这个图,我们给每一个结点加一个权值为
同样的方法可以用于求边数小于等于
习题¶
- 洛谷 P1962 斐波那契数列,即上面的例题,同题 POJ3070
- 洛谷 P1349 广义斐波那契数列,
\text{base} -
洛谷 P1939【模板】矩阵加速(数列),
\text{base} 3 \times 3 本页面部分内容译自博文 Кратчайшие пути фиксированной длины, количества путей фиксированной длины 与其英文翻译版 Number of paths of fixed length/Shortest paths of fixed length。其中俄文版版权协议为 Public Domain + Leave a Link;英文版版权协议为 CC-BY-SA 4.0。
build本页面最近更新:,更新历史
edit发现错误?想一起完善? 在 GitHub 上编辑此页!
people本页面贡献者:OI-wiki
copyright本页面的全部内容在 CC BY-SA 4.0 和 SATA 协议之条款下提供,附加条款亦可能应用