【线性代数】矩阵乘法与线性DP优化
现在有一道题目如下:
输入一个整数 n (n≤1018), 求第 n 个斐波那契数。
众所周知斐波那契数列的递推公式是:fi=fi−1+fi−2。通过O(N)的时间递推可以在 1s 内求出前 1e8 的斐波那契数,但是题目的范围是 1e18,这要怎么办呢?这时候就要引出我们今天要学习的内容——矩阵与矩阵乘法了。
什么是矩阵(matrix)#
下面是一个2×2的矩阵,其中a
,b
,c
,d
,是里面的元素,矩阵里的元素可以是数字符号或者数学式。
[acbd]
关于矩阵的其他内容我们不再延申,你现在只要知道矩阵是这么样的一个东西就可以了。
矩阵可以用字母代表,那么矩阵 An×m 本质上是一个 n 行 m 列的二维数组。
矩阵乘法#
矩阵之间可以相乘,并且满足结合律与分配律——不满足交换律,在n×m(n=m)这种不是正方形
的矩阵中,交换前后两个矩阵相乘会导致结果矩阵的形状不同,我们会在后面解释。
矩阵相乘时,相乘矩阵的宽高必须有一个相同,否则无法配对。
比如下面这个式子,把矩阵A×B=C,可以这样写:
C∗n×s=A∗n×m×B_m×s
那么它实际上就等于:
C∗ij=∑A∗ik×B_kj(1≤k≤m)
其中 k 的遍历过程也就是相乘矩阵的宽高必须有一个相同
的原因,否则匹配不了。
写成代码形式,就和弗洛伊德很像,可以把弗洛伊德看成是魔改的矩阵乘法:
用矩阵优化线性 DP#
回到前言我们说的题目,这里回顾一下:
输入一个整数 n (n≤1018), 求第 n 个斐波那契数。
我们每一次转移可以用一个矩阵表示:
[fi−1,fi−2]×[1110]=[fi,fi−1]
因为是线性的,所以我们很容易发现,每一转移其实就是当前的状态矩阵乘上我们的[1110],那么每转移一次,i 就加一,我们先处理出f1,f2,那么通过n−2次转移,我们就可以得到fn的值。
所有类似于这样的线性 DP 都可以用矩阵来转移,比如fi=fi−1+fi−3+fi−4,这种转移,我们可以构造下面这一个转移矩阵:
1011010000100001
然后使用快速幂可以把时间复杂度从O(N)优化到O(k3logN) ,其中 k 是状态是数量,也就是转移矩阵的边长。
下面是实例代码,可以解决fi=fi−1+fi−3+fi−4求解fn并且n≤1018的情况。
这里因为乘的顺序不一样(交换了),矩阵不能使用交换律,所以转移矩阵稍有不同。(实际上一般情况下习惯把系数放在第一行,也就是代码中转移矩阵的样子)