矩阵加速递推¶
题目¶
斐波那契数列是满足如下性质的一个数列:
若现在需要求出 \(F_n \bmod 10^9 + 7\) 的值。
对于 \(100\%\) 的数据,\(1\le n < 2^{63}\)。
分析¶
因为\(1\le n < 2^{63}\),暴力可以直接放弃。
这时候,我们就要引入一种递推加速技术 矩阵快速幂 。
矩阵快速幂分为矩阵以及快速幂两部分,接下来,我们将分开进行讲解。
快速幂 OI-WIKI¶
计算 a 的 n 次方表示将 n 个 a 乘在一起:
整体的时间复杂度在\(\Theta(n)\),当n达到\(10^{18}\)的规模时便会超时
不过我们知道:
二进制取幂的想法是,我们将取幂的任务按照指数的二进制表示来分割成更小的任务。
举个例子¶
首先我们将 n 表示为 2 进制,举个例子:
因为 n 有 \(\lfloor \log_2 n \rfloor + 1\) 个二进制位,因此当我们知道了 \(a^1, a^2, a^4, a^8, \dots, a^{2^{\lfloor \log_2 n \rfloor}}\) 后,我们只用计算 \(\Theta(\log n)\) 次乘法就可以计算出 \(a^n\)。
于是我们只需要知道一个快速的方法来计算上述 3 的 2^k 次幂的序列。这个问题很简单,因为序列中(除第一个)任意一个元素就是其前一个元素的平方。举一个例子:
因此为了计算 \(3^{13}\),我们只需要将对应二进制位为 1 的整系数幂乘起来就行了:
将上述过程说得形式化一些,如果把 n 写作二进制为 \((n_tn_{t-1}\cdots n_1n_0)_ 2\) ,那么有:
其中 \(n_i\in\{0,1\}\)。那么就有
根据上式我们发现,原问题被我们转化成了形式相同的子问题的乘积,并且我们可以在常数时间内从 \(2^i\) 项推出 \(2^{i+1}\) 项。
这个算法的复杂度是 \(\Theta(\log n)\) 的,我们计算了 \(\theta(\log n)\) 个 \(2^k\) 次幂的数,然后花费 \(\Theta(\log n)\) 的时间选择二进制为 1 对应的幂来相乘。
矩阵¶
矩阵乘法¶
构造中间矩阵¶
考虑初始矩阵
因为初始矩阵以及新矩阵均为2 by 1的矩阵
可推出中间矩阵为2 by 2的矩阵
最后推出来的矩阵形式便是中间矩阵与初始\(base\)矩阵相乘
将系数进行提取后可得中间矩阵为
即
矩阵快速幂¶
在推导出中间矩阵后便可从初始的\(F_1,F_2\)推导出\(F_n\)
可以看到,我们成功将其表示成了幂的形式
使用快速幂进行加速即可
Code:¶
# include<cstdio>
# include<cstring>
using namespace std;
long long n,a[3],mul[3][3],res[3][3],tmp[3][3],tp[3];
void mul_1()
{
memset(tmp,0,sizeof(tmp));
for(register int i=1;i<=2;i+=1)
for(register int j=1;j<=2;j+=1)
for(register int k=1;k<=2;k+=1)
tmp[i][j]=(tmp[i][j]+res[i][k]*mul[k][j])%1000000007;
for(register int i=1;i<=2;i+=1)
for(register int j=1;j<=2;j+=1)
res[i][j]=tmp[i][j];
}
void mul_2()
{
memset(tmp,0,sizeof(tmp));
for(register int i=1;i<=2;i+=1)
for(register int j=1;j<=2;j+=1)
for(register int k=1;k<=2;k+=1)
tmp[i][j]=(tmp[i][j]+mul[i][k]*mul[k][j])%1000000007;
for(register int i=1;i<=2;i+=1)
for(register int j=1;j<=2;j+=1)
mul[i][j]=tmp[i][j];
}
void solve()
{
for(register int i=1;i<=2;i+=1)
for(register int j=1;j<=2;j+=1)
tp[i]=(tp[i]+res[i][j]*a[j])%1000000007;
printf("%lld\n",tp[1]);
}
int main()
{
scanf("%lld",&n);
if(n<=2)printf("1\n");
else
{
a[1]=a[2]=1;
for(register int i=1;i<=2;i+=1)
res[i][i]=1;
for(register int i=1;i<=2;i+=1)
for(register int j=1;j<=2;j+=1)
mul[i][j]=1;
mul[2][2]=0;
n-=2;
while(n)
{
if(n&1)mul_1();
n>>=1;
mul_2();
}
solve();
}
return 0;
}