0x00 引入
矩阵,顾名思义,就是由数构成的矩形阵列
比如这样的:$\begin{array}{l}\begin{bmatrix}2&3&4\0&7&13\c&\alpha&\sqrt5\end{bmatrix}\\end{array}$
就是一个3*3的矩阵
矩阵在信息学乃至数学里面的用处都非常广泛,下面就来介绍下它的一些基本知识,以及常用的地方。本文同时还会介绍矩阵快速幂以及快速矩阵乘法。
0x01 何为矩阵
矩阵的定义
其实就是上面那样的啦......
定义一个n行m列的矩阵为由n*m个数构成的矩形阵列,其中“数”可以是虚数、实数、01......
矩阵的定义实际上源于线性代数中的线性映射,但是这样定义实在是过于复杂,因此一般(包括高中数学)里面都用行列的方式定义矩阵
矩阵的基本运算
矩阵的基本运算包括加法和数乘
矩阵加法
矩阵加法定义在两个行数列数相同的矩阵之间
两个n*m矩阵相加得到一个n*m矩阵,其中得到的新矩阵每个位置上的元素等于原来的两个矩阵对应位置上的元素之和
例:
$\begin{bmatrix}2&4\5&3\8&1\end{bmatrix}+\begin{bmatrix}5&3\6&7\9&2\end{bmatrix}=\begin{bmatrix}7&7\11&10\17&3\end{bmatrix}$
矩阵加法满足交换律、结合律
矩阵减法类似,不赘述
矩阵数乘
矩阵数乘定义在一个矩阵和一个数之间
一个数和一个n*m矩阵数乘,得到一个n*m矩阵,新矩阵每一个元素等于旧矩阵对应元素乘上那个数
例:
$2\ast\begin{bmatrix}2&4\5&3\8&1\end{bmatrix}=\begin{bmatrix}4&8\10&6\16&2\end{bmatrix}$
矩阵数乘满足交换律(数之间交换)、结合律(只有一个矩阵的情况下),以及分配率(一个数乘两个矩阵或者一个矩阵乘两个数都可以)
矩阵乘法
矩阵乘法是一个大话题,值得单独拿出来讲一下
矩阵乘法最初的定义是线性代数中两个线性空间之间的两个映射的“乘积”,因为一个这样的线性映射可以用矩阵表示,那么两个映射(也就是A映射到B,B映射到C)就可以了乘起来表示
当然,这篇博客不会把线性代数拖出来讲;本文只会给出矩阵乘法的运算方法——因为它在OI中大有用途
两个矩阵A与B能够进行矩阵乘法运算(A*B),当且仅当A的列数和B的行数相同(因此矩阵乘法并不满足交换律)
设A是n*m的矩阵,B是m*k的矩阵,同时设A*B=C
那么:
C矩阵是n*k的,同时C矩阵的每一个元素的值如此计算:
$c\left[i\right]\left[j\right]={\textstyle\sum_{p=1}^m}a\left[i\right]\left[p\right]\ast b\left[p\right]\left[j\right]$
也就是说,新矩阵的第(i,j)个元素相当于A的第i行和B的第j列上每个元素一一相乘再求和,这也是为什么矩阵乘法要求列数行数相同
例:
$\begin{bmatrix}10&1\end{bmatrix}\ast\begin{bmatrix}a&0\c&1\end{bmatrix}=\begin{bmatrix}10a+c&1\end{bmatrix}$
矩阵乘法不满足交换律,但是满足结合律(仅限A*B*C这样的连续矩阵乘法)以及分配率(只要进行的都是合法运算)
矩阵乘法在OI中的主要应用
在OI中,矩阵乘法主要用于解决线性递推问题
线性递推就是这样的一类递推:
$h_n=\sum_{i=1}^{n-1}a_i h_i +b$
其中$h$是递推函数,$b$是常数项,$a_i$是系数(可以为零),且右式中的所有$h_i$的次数都是一
这时我们就可以用矩阵乘法来模拟转移了
例如斐波那契数列$f_{i+2}=f_i+f_{i+1}$
它的递推可以由这两个矩阵的乘法得到:
状态矩阵$\begin{bmatrix}f_i&f_{i+1}\end{bmatrix}$
转移矩阵$\begin{bmatrix}0&1\1&1\end{bmatrix}$
显然上述两个矩阵的矩阵乘积是$\begin{bmatrix}f_{i+1}&f_{i+2}\end{bmatrix}$
然而,这里有一个问题:矩阵乘法是$O\left(nmk\right)$的,明显比原来的时间效率要低啊,那为什么要这么做呢?
答案就是矩阵快速幂
矩阵的基础操作模板在此(没有数乘和加法的部分,因为OI中不常用也不常考,而且想必是很简单的)
struct ma{
ll a[50][50],n,m;
ma(){memset(a,0,sizeof(a));n=m=0;}
void clear(){memset(a,0,sizeof(a));n=m=0;}
const ma operator *(const ma &b){
ma re;re.n=n;re.m=b.m;ll i,j,k;
for(i=1;i<=n;i++){
for(j=1;j<=b.m;j++){
for(k=1;k<=m;k++){
re.a[i][j]+=a[i][k]*b.a[k][j];
re.a[i][j]%=MOD;
}
}
}
return re;
}
const void operator =(const ma &b){
n=b.n;m=b.m;ll i,j;
for(i=1;i<=n;i++) for(j=1;j<=m;j++) a[i][j]=b.a[i][j];
}
};
0x02 矩阵快速幂
快速幂算法大家想必都很熟悉了:在$O\left(log_2 n\right)$的时间复杂度内求出一个数的n次方
那么,矩阵快速幂便是和它共同的
还是考虑斐波那契数列递推,我们发现,若设初始矩阵$\begin{bmatrix}f_1&f_2\end{bmatrix}$为$A$,而转移矩阵为$B$,那么我们要求的$f_n$就是这样得到的:
设矩阵$C=A\ast B^{n-1}$,那么显然C矩阵的第一行第二列的元素就是$f_n$的值
而由于矩阵乘法满足结合律,因此快速幂的思想在这里依旧可以使用,所以我们可以利用快速幂的思维,在很短的时间内把$B^{n-1}$求出来
那这个算法有什么优势呢?
当然是有的,比如我让你求斐波那契数列的第$10^{15}$项
此时矩阵快速幂就可以在$log 10^{15} \ast$ 矩阵乘法复杂度的时间内得出解,而这道题的矩阵乘法因为是1*2的矩阵乘2*2的,所以复杂度很低
矩阵快速幂的模板如下:
ma ppow(ma x,ma y,ll t){
while(t){
if(t&1) x=x*y;
y=y*y;t>>=1;
}
return x;
}
是不是看起来非常像实数快速幂?实际上它们俩就是一样的
什么时候可以应用矩阵快速幂呢?
第一部分里我们说过,矩阵乘法可以用于解决线性递推问题
那么我们遇到有线性递推问题的时候,就可以方便地使用矩阵快速幂了
上面的斐波那契数列是一个例子,同时还有几个例题:
可以看到,矩阵快速幂可以解决单个递推、多组递推、多组递推加上非线性常数的递推,甚至可以结合dp一起做,作为dp的优化
一般而言,我们都会构造一个一行k列的状态矩阵以及一个k行k列的转移矩阵
由于这种情况下新矩阵的每个格子互相独立,因此比较方便我们设计转移矩阵
在矩阵快速幂中,我们只要确定了状态矩阵和转移矩阵,那么问题一般就迎刃而解了
0x03 快速矩阵乘法
挖坑++......一周内补上