矩阵乘法和矩阵加速
矩阵乘法
两个矩阵相乘,前提是一个矩阵的列数必须等于另一个矩阵的行数。矩阵乘法的规则是拿左边的一行与右边的一列中的元素先相乘再相加。形式上就是如果\(A_{n,m}\times B_{m,q}\),答案矩阵\(C\)中
配图理解。

容易发现一些规律(以\(A_{n,m}\times B_{m,q}\)为例):
1.相乘的得到的矩阵\(C\)一定为\(n\times q\)。
2.矩阵乘法不满足交换律,但满足结合律,即\((AB)C=A(BC)\)。
矩阵乘法可以用左行右列的口诀来记忆。
还有一个单位矩阵,它一定是个方阵(行数和列数相等),从左上角到右下角的一条对角线(称为主对角线)上的元素值均为\(1\),其余位置都是\(0\)。容易发现,任意矩阵与单位矩阵相乘都等于它本身。
矩阵快速幂
这个东西使上面说的变得有用。对于一个矩阵\(A\),如何高效地计算\(A^k\)的值?我们已经熟知了快速幂,那么其实套用一下快速幂板子,把里面的乘法运算改成矩阵乘法即可。
void Pre(){
for(int i = 1; i <= n; i++) res[i][i] = 1;//构建单位矩阵作为初始的答案数组,相当于普通快速幂中res=1这一句
}
void mulres(){
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++) b[i][j] = 0;
}
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++){
for(int k = 1; k <= n; k++){
(b[i][j] += res[i][k] * a[k][j] % zky) %= zky;
}
}
}
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++) res[i][j] = b[i][j];
}
}
void mulx(){
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++) b[i][j] = 0;
}
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++){
for(int k = 1; k <= n; k++){
(b[i][j] += a[i][k] * a[k][j] % zky) %= zky;
}
}
}
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++) a[i][j] = b[i][j];
}
}
void Quick(ll k){
while(k){
if(k % 2ll == 1ll) mulres();//乘->矩乘
mulx();//乘->矩乘
k /= 2ll;
}
}
好像不建议使用运算符重载,原因是可能会慢上许多。
快速幂的复杂度是\(\log\)的,矩阵乘法复杂度是\(n^3\)的,因此总复杂度为\(O(n^3\log n)\)。注意这里的\(n\)表示的是矩阵大小。
矩阵加速
对于一些递推式子,可以通过矩阵来加速计算。最经典的例子就是斐波那契数列。规定\(f_1=f_2=1\),\(f_i=f_{i-1}+f_{i-2}(i\geq 3)\),计算\(f_n\)的值。我们可以在\(O(n)\)的时间内用递推来解决,但是当\(n\)的范围达到\(1e18\)时,必须使用\(\log\)级别的做法。
考虑把上述的递推式扔进矩阵里。
记
那么我们一开始就知道\(f_1\)和\(f_2\)的值,经过一次\(A\times B\)的运算,就可以得到\(f_3\)的值。经过\(A\times B\times B\),就可以得到\(f_4\)的值……由于矩阵乘法满足结合律,所以有\(f_n=B\times A^{n-2}\)。
总体复杂度为\(O(\log n)\),略带常数。
由此可见,如果能根据递推式子列出一个常数级别的转移矩阵并利用它进行转移,就可以用矩阵乘法优化递推。当你看到题目中有类似\(1e18\)这样的大规模数据,并且可以列递推式,就要往矩阵加速的方面来考虑。
来个例题吧P2106。
广义矩阵乘法
上面先乘再加(\((+,\times)\))的运算是一般的矩乘法则,事实上还有一些矩乘法则可能会在题目中用到,比如先加然后取最小值(\((\min,+)\)),像这样的非传统的矩阵乘法称为广义矩阵乘法。对于一些涉及到取\(\min\)、\(\max\)和加操作的递推式子,需要灵活运用矩乘。放一道我自己出的板子。

浙公网安备 33010602011771号