T0004 Fibonacci问题

题目描述:

斐波那契数列,是一个数学上著名的数列,这个数列{An}是这样的:1 1 2 3 5 8…
除了第一、二项之外,以后每项都是前两项的和
现给定n,请输出An。由于结果可能比较大,请输出An对19999999取余之后的结果

输入示例:

5

输出示例:

5

数据范围:

对于50%的数据,1≤n≤10^5
对于80%的数据,1≤n≤10^8
对于100%的数据,1≤n≤10^10

时间限制:

1s

空间限制:

128MB

题目分析:

对于斐波那契数列的描述,已经在题目描述中描述的非常清楚。但是这次的数据范围非常大,使用朴素的O(n)算法超时对于部分数据来说是必不可少的。因此我们需要寻找对数级别的算法,即O(logn)式算法,这就需要我们对题目进行分析。
首先我们对数据进行分析,对于100%的数据来说,1≤n≤10^10。要想拿到100%的分数,对于数据类型又是一个极大的挑战。long long int可能也没辙。出题者考虑到这一点,决定对数据进行大规模取余运算。这样的问题有两种解决方案,一是对n数据使用高精度计算,这暂时超出了我们目前的学习范围。二是利用初等的数论知识,对取余进行优化。
我们先来学习取余的几个性质:
1)
(a*b)%c=((a%c)*(b%c))%c
2)(a+b)%c=((a%c)+(b%c))%c
我们现假定这两个性质在数学上是正确的而略去其证明。
取余优化之后,我们回到最初的问题,该怎么找到一个时间复杂度为O(logn)的算法,来对计算斐波那契数列进行优化。
我们先来对斐波那契数列的通项公式进行分析:
An={1 (n=1,2)
An-1+an-2(n≥3)
}
好,这里我们就需要一些高等代数的知识了。
我们假定读者已经学过线性代数,并且可以基本掌握矩阵乘法的知识。
两个矩阵相乘首先要满足第一个矩阵的列数等于第二个矩阵的行数,而且要记住一点,矩阵乘法是不满足交换律的。至于矩阵乘法怎么算,假设A为m*p的一个矩阵,B为p*n的一个矩阵,用i表示行数,j表示列数,那A*B的结果中任意一项为 (AB)ij=pk=1aikbkj=ai1b1j+ai2b2j+...+aipbpj
我们构造以下两个矩阵:

[11][1110]
运用矩阵乘法的运算,得到
[21]

继续:
[21][1110]=[32]

继续:
[32][1110]=[53]

以此类推,可以发现矩阵1行1列所代表的数字,是fibonacci数列中的某一项,则fibonacci可以化成算这样的矩阵乘法:
([11][1110])n1

但是我们发现,算一个数的幂的时间复杂度为O(n),依然无法解决我们开头说的找一个对数级别的复杂度算法,因此我们需要一个快速算幂的算法。此处简介快速幂算法。如果n为偶数,那我们可以将其化为 an=an2an2 ,如果n为奇数,则对n/2向下取整并再乘以一个n
比如, a3=a1a1a (1为 32 的向下取整)。
分解到不能再分解后,用数学方法可以证明,快速幂算法的时间复杂度为O(logn)。

long long int quick_power(long long int a, long long int n)  // 使用快速幂算法计算 a^n
{
        long long int ans = 1;
        while (n != 0) // 当 n 不为 0 时循环
        {
            if (n % 2 == 1) // 判断 n 是否为奇数
            {
                ans = (ans % 19999999 * a % 19999999) % 19999999; // 如果此时 n 是奇数,那么给结果再乘一个 a
            }
            a = a * a % 19999999;
            n = n / 2; //给 n 整除 2,使规模缩小一半
        }
        return ans % 19999999;
}

此处就完成了对快速幂的计算。
对快速幂的计算还可以使用基本运算符来计算,此处暂不讲,在讲解快速幂算法的时候展开讲述。
标准代码:

#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;

struct matrix
{
    long long int a[2][2];

    matrix()
    {
        memset(a, 0, sizeof(a));//初始化矩阵
    }

    void init()//对公共矩阵进行定义
    {
        a[0][0] = 1;
        a[0][1] = 1;
        a[1][0] = 1;
        a[1][1] = 0;
    }

    matrix operator*(matrix t)//乘号运算符重载
    {
        matrix tmp;
        for (int i = 0; i < 2; i++)
        {
            for (int j = 0; j < 2; j++)
            {
                for (int k = 0; k < 2; k++)
                {
                    tmp.a[i][j] += (this->a[i][k]) * t.a[k][j];
                }
                tmp.a[i][j] %= 19999999;
            }
        }
        return tmp;
    }

    matrix operator^(long long int t)//运算符重载的矩阵计算
    {
        matrix tmp;
        tmp.a[0][0] = 1;
        tmp.a[0][1] = 0;
        tmp.a[1][0] = 0;
        tmp.a[1][1] = 1;
        matrix myself;
        for (int i = 0; i < 2; i++)
        {
            for (int j = 0; j < 2; j++)
            {
                myself.a[i][j] = this->a[i][j];
            }
        }
        while (t)
        {
            if (t & 1)
            {
                tmp = tmp * myself;
            }
            myself = myself * myself;
            t >>= 1;
        }
        return tmp;//快速幂的代码实现,以后会详细讲,此处运用了位运算
    }
};

int main()
{
    freopen("fibonacci.in", "r", stdin);
    freopen("fibonacci.out", "w", stdout);
    long long int n = 0;
    matrix t, p;
    t.init();
    cin >> n;
    p = t ^ (n - 1);
    cout << p.a[0][0] % 19999999;
    return 0;

此处采用了运算符重载技术,是C++语言的特性,对于没有学过C++的读者来说,可以编写矩阵乘法函数,其实现方法与该重载运算符类似,此处为笔者参照网上的代码:

#include <iostream>    
#include <cstdio> 
typedef long long LL;    
const LL maxn=1000+10;  
const LL mod=19999999;  
const int N=2;  
using namespace std;   
struct Matrix  
{  
    LL m[N][N];  
//二元矩阵的建造
};  
Matrix A=  
{  
    1,1,  
    1,0  
};  
Matrix I=  
{  
    1,0,  
    0,1  
};  
Matrix multi(Matrix a,Matrix b)  
//矩阵乘法
{  
    Matrix c;  
    for(int i=0;i<N;i++)  
    {  
        for(int j=0;j<N;j++)  
        {  
            c.m[i][j]=0;  
            for(int k=0;k<N;k++)  
                c.m[i][j]+=a.m[i][k]*b.m[k][j]%mod;  
//利用了取余的运算性质

            c.m[i][j]%=mod;  
        }  
    }  
    return c;  
}  
Matrix power(Matrix A,int k)  
//快速幂的实现
{  
    Matrix ans=I,p=A;  
    while(k)  
    {  
        if(k&1)  
        {  
            ans=multi(ans,p);  
            k--;  
        }  
        k>>=1;  
        p=multi(p,p);  
    }  
    return ans;  
}  
int main()  
{  
    int n;  
    scanf("%d",&n);
    Matrix ans =power(A,n-1);  
    printf("%lld\n",ans.m[0][0]); 
    return 0;  
}  

http://blog.csdn.net/liangzhaoyang1/article/details/51428386
http://codevs.cn/problem/2834/

原稿:贺恩泽 2017.10.01
整理者:李家康 2017.10.01

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值