P1939 矩阵加速(数列) python 题解(矩阵快速幂)

· · 题解

本题为 P1962、P1349 的小变形,题解链接如下。

P1962 斐波那契数列 python 题解(矩阵快速幂)

P1349 广义斐波那契数列 python 题解(矩阵快速幂)

使用矩阵快速幂加速递推,关键在于构造出对应的矩阵。以本题为例,首先定义初始的列向量 V = \left[ \begin{array}{c} 0 \\ a_1 \\ a_2 \\ a_3 \end{array} \right] = \left[ \begin{array}{c} 0 \\ 1 \\ 1 \\ 1 \end{array} \right],然后将递推式 a_x=a_{x-1}+a_{x-3} 用矩阵表示。

由于 \begin{aligned} \left[ \begin{array}{c} a_{i-3} \\ a_{i-2} \\ a_{i-1} \\ a_{i-1} + a_{i-3} \end{array} \right] = \left[ \begin{array}{ccc} 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \\ 0 & 1 & 0 & 1 \end{array} \right] \left[ \begin{array}{c} a_{i-4} \\ a_{i-3} \\ a_{i-2} \\ a_{i-1} \end{array} \right] \end{aligned},我们可以定义矩阵 $A = \left[ \begin{array}{ccc} 0 & 1 & 0 & 0 \ 0 & 0 & 1 & 0 \ 0 & 0 & 0 & 1 \ 0 & 1 & 0 & 1 \end{array} \right]

因此,若要求数列的第 $n$ 项,只要令 $V = A^nV$,此时 $V$ 的第一个元素就是答案了。其中 $A^n$ 可以用矩阵快速幂求出来,时间复杂度为 $O(logn)$。矩阵快速幂和普通快速幂几乎一样,只是把乘法改为了矩阵乘法。在初始化结果时,普通快速幂应初始化为 $1$,矩阵快速幂则应初始化为单位矩阵 $I$。 --- 本题解给出 python 代码,可以直接将二维 ```list``` 作为函数的参数和返回值。如果用 c++,由于二维数组作为参数和返回值时比较麻烦,一个简单的方法是把矩阵封装为结构体,然后将该结构体作为函数的参数与返回值即可。 代码如下(python): ```python import sys MOD = 1e9 + 7 MOD = int(MOD) # 矩阵乘法,传入两个矩阵 a 和 b,返回矩阵乘法 a*b 得到的结果矩阵 def matmul(a, b): if len(a[0]) != len(b): # a 的列 != b 的行,乘不了 sys.stderr.write("size error\n") sys.exit(1) row = len(a) # 结果的行是 a 的行 col = len(b[0]) # 结果的列是 b 的列 tmp = len(b) # a 的列 || b 的行,即共享维度 result = [[0 for _ in range(col)] for _ in range(row)] for i in range(row): for j in range(col): for k in range(tmp): result[i][j] += a[i][k] * b[k][j] result[i][j] %= MOD return result # 矩阵快速幂,a 为矩阵,b 为要求的次幂 def quick_pow(a, b): row = len(a) col = len(a[0]) # 结果初始化为单位矩阵(直接用列表生成式即可生成单位矩阵,内层的那部分式子相当于三目运算符) result = [[1 if i == j else 0 for j in range(col)] for i in range(row)] while b > 0: if b & 1: # 矩阵乘法一般不满足交换律,但由于 result 是 a 的整数次幂,根据结合律 a 乘在哪边都行 # 不过我们进行线性变换是左乘,所以乘在左边更符合直觉 result = matmul(a, result) b >>= 1 a = matmul(a, a) return result matrix = [[0, 1, 0, 0], # 单次线性变换对应的矩阵 [0, 0, 1, 0], [0, 0, 0, 1], [0, 1, 0, 1]] vector = [[0], # 初始列向量 [1], [1], [1]] T = int(input()) while T > 0: n = int(input()) # n 代表矩阵是几次幂,不能取模,一开始铸币取模了 change = quick_pow(matrix, n) # 用矩阵快速幂得到 n 次变换对应的矩阵 result = matmul(change, vector) # 将变换利用矩阵乘法应用到初始列向量上 print(result[0][0]) T -= 1 ```