求斐波那契数列的第n项

来源:互联网 发布:网络教育网络统考 编辑:程序博客网 时间:2024/06/08 06:14

提要

本文介绍了4种(3种?)求斐波那契数列第n项的方法。

斐波那契数列简介

斐波那契数列(Fibonacci sequence),又称黄金分割数列、因数学家列昂纳多·斐波那契(Leonardoda Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:1、1、2、3、5、8、13、21、34、……在数学上,斐波纳契数列以如下被以递归的方法定义:F(0)=1F(1)=1,F(n)=F(n1)+F(n2)(n>=2nN)

解法:

0.按照递推式直接计算,时间复杂度O(n)

1.矩阵快速幂

矩阵乘法和矩阵快速幂的简介请看我的另一篇博客:http://blog.csdn.net/George__Yu/article/details/77231013

现在我们有这样一个矩阵A:(F[i1]F[i])

然后我们构造一个矩阵

B=(0111)

见证奇迹的时刻到了!

我们发现:AB=(F[i]F[i+1])(大家可以自己手算一下)

若i刚开始时等于1,即A=(01),那么 A 乘上 n 次 B 后,A12就是斐波那契数列的第 n 项。

又因为矩阵乘法具有结合律,所以我们可以把Bn先用矩阵快速幂算出来,再乘上 A 即可。

这样,我们把递推斐波那契数列第 n 项的时间复杂度从O(n)降到了O(log2n)

很神奇吧?

求斐波那契数列第n项的代码:

//矩阵快速幂求斐波那契数列第n项#include <iostream> #include <cstdio>#include <cstring>#include <cstdlib>#include <algorithm>#include <cmath>using namespace std;typedef long long LL;struct matrix{    LL z[5][5];    int m,n;}origin,res,fib;LL n;LL p=1e9+7;void print(matrix x){    for(int i=1;i<=x.m;i++)    {        for(int j=1;j<=x.n;j++)            printf("%d ",x.z[i][j]);        printf("\n");    }    printf("\n");}matrix mul(matrix x,matrix y)//(AB)ij=∑Aik*Bkj=Ai1*B1j+Ai2*B2j+...+Aik*Bkj {    matrix t;    t.m=x.m;    t.n=y.n;    memset(t.z,0,sizeof(t.z));    for(int a=1;a<=x.m;a++)        for(int b=1;b<=x.n;b++)            for(int c=1;c<=y.n;c++)                t.z[a][c]+=x.z[a][b]*y.z[b][c];    for(int i=1;i<=x.m;i++)        for(int j=1;j<=x.n;j++)             t.z[i][j]%=p;    return t;}void matrixpow(LL n){    while(n)    {        if(n&1) res=mul(res,origin);        origin=mul(origin,origin);        n>>=1;    }}void init(){    scanf("%lld",&n);    origin.z[2][1]=origin.z[2][2]=origin.z[1][2]=1;    //构造斐波那契数列的递推矩阵 | 0 1 |     origin.m=origin.n=2;//   | 1 1 |     for(int i=1;i<=2;i++) res.z[i][i]=1;//构造单位矩阵  | 1 0 |     res.m=res.n=2;                      //            | 0 1 |      fib.z[1][2]=1;//构造斐波那契数列第0项和第一项的矩阵| 0 1 |    fib.m=1;fib.n=2;}int main(){    init();    matrixpow(n);    fib=mul(fib,res);    printf("%lld\n",fib.z[1][1]%p);    return 0;}

推广:

然而问题还没结束。

矩阵B是怎么构造出来的?

若要求递推的数列是F[i]=aF[i1]+bF[i2]怎么办?

这时怎么构造矩阵B?

其实很简单。

这时矩阵A=(F[i1]F[i])

我们不妨设矩阵B=(xzyw)

因为我们希望AB=(F[i]F[i+1])=(F[i]aF[i]+bF[i1])

根据矩阵乘法的定义又有AB=(xF[i1]+zF[i]yF[i1]+wF[i])

那么容易知道x=0,y=b,z=1,w=a

所以B=(01ba)

所以F[n]=(A×Bn)12

大家可以直接记住这个结论,如果记不住的话,在a,b已知时可以按刚才的方法解方程求x,y,z,w。

再推广:

如果递推式不止两项怎么办?比如这样:

F[i]=a1F[i1]+a2F[i2]++akF[ik]F[1],F[2],F[3],,F[k]=1

还是按刚才的办法求B矩阵!

这里我直接给出结果,有兴趣的朋友可以自己研究一下。

A=(F[ik+1]F[ik+2]F[ik+3]F[i])

B=010000010000010akak1ak2ak3a1

AB=(F[ik+2]F[ik+3]F[ik+4]F[i+1])

B矩阵是 k 行 k 列的矩阵。

这时用矩阵快速幂递推的时间复杂度是O(k2log2n),而直接递推的复杂度是O(nk)

一般 n 都是远大于 k 的,所以绝大多数情况还是矩阵快速幂快。

(PS:我应该没算错,如果大家发现我算错了可以在评论中指出来qwq)

2.利用斐波那契数列的二倍项公式

二倍项公式:

F[2n]=F[n+1]2F[n1]2=(2F[n1]+F[n])F[n](1)

F[2n+1]=F[n+1]2+F[n]2(2)

通过这个公式,可以分治地求出斐波那契数列的第n项。

若n是偶数,我们想要知道F[n],只需要知道F[n/2]和F[n/2-1];

若n是奇数,我们想要知道F[n],只需要知道F[n/2+1]和F[n/2](除法默认向下取整)。

在 n 极大的时候(如1018左右),因为空间不够,我们用map而不是数组来储存中间结果。

由于我们每次都把数据折半,因此空间复杂度并不高,map所用的空间和递归所用的栈空间都是log2n级别的。

然而在时间复杂度上,因为用了map和递归计算,所以要比矩阵快速幂慢一些。大概是O(log2n)

无论从空间上还是时间上,这种方法都要劣于矩阵快速幂。如果不会矩阵快速幂,可以先用这种方法。

代码:

#include <iostream>#include <cstdio>#include <algorithm>#include <cstdlib>#include <cstring>#include <map>#define mod 1000000007using namespace std;typedef long long LL;LL n;map<LL,LL>m;map<LL,LL>::iterator it;//定义一个迭代器 LL F(LL i)//递归求解 {    LL res1,res2,res;    if(i<3) return 1; //F[1]=F[2]=1    it=m.find(i);    if(it==m.end())//未算过    {         if(i&1)//奇数使用公式(2)        {             res1=F(i>>1);            res2=F((i+1)>>1);            res=(res1*res1+res2*res2)%mod;        }        else//偶数使用公式(1)        {             res1=F((i-2)>>1);            res2=F(i>>1);            res=((res1<<1)+res2)*res2%mod;        }        m[i]=res;//记录        return res;    }    else        return it->second; //算过直接返回}int main(){    scanf("%lld", &n);    printf("%lld\n", F(n));    return 0;}

3.分段打表

如果数据范围是 n <= x,那么我们可以先把F[x]F[2x],F[3x],,F[x]都算出来,从这些项开始递推。这种方法的时间复杂度和空间复杂度都是O(n),而且要求 n 不能太大,在考试时间范围内能用暴力方法把F[x]先算出来。

代码就不贴了,这种方法比较玄学,大家也不一定非要以x为长度来打表,只要保证每一段的长度能在限定时间内算出来就可以(一般是107左右),自己意会一下就好(雾。

注意:这种方法只在n稍大于1e8的时候比较有用,比如1e10左右。n太大时还是乖乖用上面两种方法吧。

例题

洛谷【p1962】斐波那契数列

https://www.luogu.org/problem/show?pid=1962

题目大意:求斐波那契数列的第n项,n<=1018。(并不能分段打表233)

大家可以当模板题交一下自己的代码。

The End

原创粉丝点击