这是 C 社算法团队的第一次活动。我们介绍了斐波那契数列的第n项求解方法,主要运用了矩阵快速幂算法。
斐波那契数列:
Fn=⎩⎨⎧0,1,Fn−2+Fn−1,n=0n=1n>1给定n,求Fn mod 109+7。
数据规模 | 内存限制 | 运行时间 |
---|
0≤n≤1019 | 64MB | 1.0s |
1019显然灭掉了所有用循环解决的想法。有没有比简单的O(n)更好一点的方法?用矩阵快速幂,可以达到O(logn)。观察到:
(Fn+1Fn+2)=(Fn+1Fn+Fn+1)=(0111)(FnFn+1)
这一步对于所有递推数列都是适用的,因此在有经验之后应该非常容易得到。一般地,对于Fn+2=aFn+bFn+1,有
(Fn+1Fn+2)=(Fn+1aFn+bFn+1)=(0a1b)(FnFn+1)
从递推式中有
(Fn+mFn+m+1)=(0111)m(FnFn+1)
取n=0,得到
(FmFm+1)=(0111)m(F0F1)
因此把问题转化成了如何求矩阵m次方的问题。如果设m=20a0+21a1+22a2+…(也就是把m用二进制表示),那么有
(0111)m=((0111)1)a0×((0111)2)a1×((0111)4)a2…
而这些矩阵的2k次方,完全可以预处理。当m的数量级为1019时,k<log21019<64,最多只需要存储 63 个矩阵。并且
(0111)2k=(0111)2k−1×(0111)2k−1
这些乘方可以在O(logm)时间内得到。这便是快速幂的思想:计算所有的2k次方,然后把其中需要的那些组合起来即可。
下面是 C++ 代码,其中最繁琐的部分是实现矩阵乘法:
#include <iostream>
#include <cmath>
using namespace std;
struct mat {
unsigned long long a[4];
mat operator *(mat o) {
mat t;
t.a[0] = (this->a[0] * o.a[0] + this->a[1] * o.a[2]) % 1000000007;
t.a[1] = (this->a[0] * o.a[1] + this->a[1] * o.a[3]) % 1000000007;
t.a[2] = (this->a[2] * o.a[0] + this->a[3] * o.a[2]) % 1000000007;
t.a[3] = (this->a[2] * o.a[1] + this->a[3] * o.a[3]) % 1000000007;
return t;
}
};
mat mat_pow[64];
int fib(unsigned long long k) {
mat tmp;
tmp.a[0] = 1;
tmp.a[1] = 0;
tmp.a[2] = 0;
tmp.a[3] = 1;
for (int i = 0; i < 64; i++) {
if (k & (1ull << i)) {
tmp = tmp * mat_pow[i];
}
}
return tmp.a[1];
}
int main() {
mat_pow[0].a[0] = 0;
mat_pow[0].a[1] = 1;
mat_pow[0].a[2] = 1;
mat_pow[0].a[3] = 1;
for (int i = 1; i < 64; i++) {
mat_pow[i] = mat_pow[i-1] * mat_pow[i-1];
}
unsigned long long n;
cin >> n;
cout << fib(n) << endl;
return 0;
}
补充一下矩阵的乘法公式:
(a0a2a1a3)×(b0b2b1b3)=(a0b0+a1b2a2b0+a3b2a0b1+a1b3a2b1+a3b3)
(a0a2a1a3)×(b0b1)=(a0b0+a1b1a2b0+a3b1)