【题目链接】
【题目考点】
1. 矩阵加速递推
矩阵加速递推步骤:
- 根据递推关系确定状态转移矩阵。
- 对状态转移矩阵求快速幂。
- 求幂后的状态转移矩阵乘以初始状态矩阵,得到最终状态。
设计转移矩阵的核心思路为:递推式右侧的每一项,在等号左侧都要出现其下一项。
该算法的时间复杂度为 O ( d 3 log n ) O(d^3\log n) O(d3logn),其中 d d d为方阵的阶, n n n为递推的次数,即转移矩阵的指数。
2. 矩阵快速幂
【解题思路】
设 f i f_i fi是斐波那契数列第 i i i项,斐波那契数数列的递推式为 f i = f i − 1 + f i − 2 f_i=f_{i-1}+f_{i-2} fi=fi−1+fi−2
如果直接递推求斐波那契数列,时间复杂度为 O ( n ) O(n) O(n),1s时间内只能求出 10 8 10^8 108项。而本题求 f n f_n fn, n n n最大达到 10 9 10^9 109,因此普通递推无法解决本题。
如果将状态转移方程写成状态转移矩阵,通过矩阵运算的方式完成状态转移,则可以通过快速幂来加速递推过程。
f i = f i − 1 + f i − 2 f_i=f_{i-1}+f_{i-2} fi=fi−1+fi−2写成矩阵(向量)乘法形式为:
f i = [ 1 1 ] [ f i − 1 f i − 2 ] f_i=\begin{bmatrix} 1 & 1\end{bmatrix} \begin{bmatrix} f_{i-1}\\f_{i-2}\end{bmatrix} fi=[11][fi−1fi−2]
同理
f i + 1 = [ 1 1 ] [ f i f i − 1 ] f_{i+1}=\begin{bmatrix} 1 & 1\end{bmatrix} \begin{bmatrix} f_{i}\\f_{i-1}\end{bmatrix} fi+1=[11][fifi−1]
要想得到 f i + 1 f_{i+1} fi+1,需要递推求出 [ f i f i − 1 ] \begin{bmatrix} f_{i}\\f_{i-1}\end{bmatrix} [fifi−1]
而这一项应该通过已知的 [ f i − 1 f i − 2 ] \begin{bmatrix} f_{i-1}\\f_{i-2}\end{bmatrix} [fi−1fi−2]推出。
假设存在一个转移矩阵 [ a 11 a 12 a 21 a 22 ] \begin{bmatrix} a_{11} & a_{12}\\ a_{21} & a_{22}\end{bmatrix} [a11a21a12a22]满足
f i f i − 1 \] = \[ a 11 a 12 a 21 a 22 \] \[ f i − 1 f i − 2 \] \\begin{bmatrix} f_{i}\\\\f_{i-1}\\end{bmatrix}=\\begin{bmatrix} a_{11} \& a_{12}\\\\ a_{21} \& a_{22}\\end{bmatrix}\\begin{bmatrix} f_{i-1}\\\\f_{i-2}\\end{bmatrix} \[fifi−1\]=\[a11a21a12a22\]\[fi−1fi−2
根据矩阵乘法原理,得:
f i = a 11 f i − 1 + a 12 f i − 2 f_i=a_{11}f_{i-1}+a_{12}f_{i-2} fi=a11fi−1+a12fi−2
f i − 1 = a 21 f i − 1 + a 22 f i − 2 f_{i-1}=a_{21}f_{i-1}+a_{22}f_{i-2} fi−1=a21fi−1+a22fi−2
易知:
f i = 1 ⋅ f i − 1 + 1 ⋅ f i − 2 f_i=1\cdot f_{i-1}+1\cdot f_{i-2} fi=1⋅fi−1+1⋅fi−2
f i − 1 = 1 ⋅ f i − 1 + 0 ⋅ f i − 2 f_{i-1}=1\cdot f_{i-1}+0\cdot f_{i-2} fi−1=1⋅fi−1+0⋅fi−2
因此转移矩阵为 [ 1 1 1 0 ] \begin{bmatrix} 1 & 1\\ 1 & 0\end{bmatrix} [1110]
或者根据递推式右侧的每一项,在等号左侧都要出现其下一项
已知: f i = f i − 1 + f i − 2 f_i=f_{i-1}+f_{i-2} fi=fi−1+fi−2
f i − 1 f_{i-1} fi−1的下一项是 f i f_i fi,在等号左侧已经出现。
f i − 2 f_{i-2} fi−2的下一项是 f i − 1 f_{i-1} fi−1,需要在等号左侧出现,即需要写出 f i − 1 f_{i-1} fi−1等于什么。已有的等号有边的量存在 f i − 1 f_{i-1} fi−1,自然就可以写为 f i − 1 = f i − 1 f_{i-1}=f_{i-1} fi−1=fi−1,这就是第二个递推式,二者可以整合为上述矩阵形式。
矩阵形式的状态转移方程为:
f i f i − 1 \] = \[ 1 1 1 0 \] \[ f i − 1 f i − 2 \] \\begin{bmatrix} f_{i}\\\\f_{i-1}\\end{bmatrix}=\\begin{bmatrix} 1 \& 1\\\\ 1 \& 0\\end{bmatrix}\\begin{bmatrix} f_{i-1}\\\\f_{i-2}\\end{bmatrix} \[fifi−1\]=\[1110\]\[fi−1fi−2
因此
f n f n − 1 \] = \[ 1 1 1 0 \] \[ f n − 1 f n − 2 \] = \[ 1 1 1 0 \] 2 \[ f n − 2 f n − 3 \] = . . . = \[ 1 1 1 0 \] n − 2 \[ f 2 f 1 \] \\begin{bmatrix} f_{n}\\\\f_{n-1}\\end{bmatrix}=\\begin{bmatrix} 1 \& 1\\\\ 1 \& 0\\end{bmatrix}\\begin{bmatrix} f_{n-1}\\\\f_{n-2}\\end{bmatrix}=\\begin{bmatrix} 1 \& 1\\\\ 1 \& 0\\end{bmatrix}\^2\\begin{bmatrix} f_{n-2}\\\\f_{n-3}\\end{bmatrix}=...=\\begin{bmatrix} 1 \& 1\\\\ 1 \& 0\\end{bmatrix}\^{n-2}\\begin{bmatrix} f_{2}\\\\f_{1}\\end{bmatrix} \[fnfn−1\]=\[1110\]\[fn−1fn−2\]=\[1110\]2\[fn−2fn−3\]=...=\[1110\]n−2\[f2f1
其中:
实际实现时,为了实现方便, [ f n f n − 1 ] \begin{bmatrix} f_{n}\\f_{n-1}\end{bmatrix} [fnfn−1]和 [ f 2 f 1 ] \begin{bmatrix} f_{2}\\f_{1}\end{bmatrix} [f2f1]都使用方阵表示,即:
f n 0 f n − 1 0 \] = \[ 1 1 1 0 \] n − 2 \[ f 2 0 f 1 0 \] \\begin{bmatrix} f_{n}\&0\\\\f_{n-1}\&0\\end{bmatrix}=\\begin{bmatrix} 1 \& 1\\\\ 1 \& 0\\end{bmatrix}\^{n-2}\\begin{bmatrix} f_{2}\&0\\\\f_{1}\&0\\end{bmatrix} \[fnfn−100\]=\[1110\]n−2\[f2f100
- 如果求 f 1 、 f 2 f_1、f_2 f1、f2,需要进行特判,直接输出 f 1 f_1 f1的值 1 1 1或 f 2 f_2 f2的值1 。
- 如果 n > 2 n>2 n>2,求 f n f_n fn,则使用矩阵快速幂求出 [ 1 1 1 0 ] n − 2 \begin{bmatrix} 1 & 1\\ 1 & 0\end{bmatrix}^{n-2} [1110]n−2,而后使用上述矩阵递推关系求出 f n f_n fn。
该算法的时间复杂度为 O ( log n ) O(\log n) O(logn)
【题解代码】
解法1:矩阵加速
cpp
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
LL n, m;
struct Mat
{
LL n, a[3][3] = {};
Mat(){}
Mat(int _n, int val = 0):n(_n)
{
for(int i = 1; i <= n; ++i)
a[i][i] = val;
}
LL* operator [] (int i)
{
return a[i];
}
Mat operator * (Mat b)
{
Mat r(n);
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= n; ++j)
for(int k = 1; k <= n; ++k)
r[i][j] = (r[i][j]+a[i][k]*b[k][j])%m;
return r;
}
};
Mat fastPow(Mat a, LL b)//矩阵快速幂
{
Mat r(2, 1);
while(b > 0)
{
if(b%2 == 1)
r = r*a;
a = a*a;
b /= 2;
}
return r;
}
int main()
{
cin >> n >> m;
if(n <= 2)
{
cout << 1%m;
return 0;
}
Mat t(2), f(2);//t:转移矩阵 f:初始矩阵
t.a[1][1] = 1, t.a[1][2] = 1, t.a[2][1] = 1;
f.a[1][1] = 1, f.a[2][1] = 1;
Mat r = fastPow(t, n-2)*f;//r:结果矩阵
cout << r.a[1][1];
return 0;
}