求斐波那契数列的第n项

提要

本文介绍了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 1000000007
using 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

  • 10
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
【问题描述】 【问题描述】编写函数f,功能是用递归的方法斐波数列的第n项,函数原型为 int f(int n),在主函数中输入一个正整数n,调用函数f斐波数列的第n项,并在主函数中输出。 斐波数列:1,1,2,3,5,8,13,21…… 【输入形式】3 【输出形式】2 【样例输入】6 【样例输出】8 【问题描述】编写函数f,功能是用递归的方法斐波数列的第n项,函数原型为 int f(int n),在主函数中输入一个正整数n,调用函数f斐波数列的第n项,并在主函数中输出。 斐波数列:1,1,2,3,5,8,13,21…… 【输入形式】3 【输出形式】2 【样例输入】6 【样例输出】8 【问题描述】编写函数f,功能是用递归的方法斐波数列的第n项,函数原型为 int f(int n),在主函数中输入一个正整数n,调用函数f斐波数列的第n项,并在主函数中输出。 斐波数列:1,1,2,3,5,8,13,21…… 【输入形式】3 【输出形式】2 【样例输入】6 【样例输出】8 【问题描述】编写函数f,功能是用递归的方法斐波数列的第n项,函数原型为 int f(int n),在主函数中输入一个正整数n,调用函数f斐波数列的第n项,并在主函数中输出。 斐波数列:1,1,2,3,5,8,13,21…… 【输入形式】3 【输出形式】2 【样例输入】6 【样例输出】8 斐波数列:1,1,2,3,5,8,13,21…… 【输入形式】3 【输出形式】2 【样例输入】6 【样例输出】8

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值