矩阵快速幂(斐波那契数列) 洛谷1962

题目背景
大家都知道,斐波那契数列是满足如下性质的一个数列:

Fn​={1 (n≤2)

        Fn−1​+Fn−2​ (n≥3)​

题目描述
请你求出 Fn mod 10^9 + 7的值。

输入格式
一行一个正整数 n

输出格式
输出一行一个整数表示答案。

输入输出样例
输入 #1复制
5
输出 #1复制
5
输入 #2复制
10
输出 #2复制
55
说明/提示
【数据范围】
对于 60% 的数据,1≤n≤92;
对于 100% 的数据,1}1≤n<2^63

3.24:

(40分)代码:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<algorithm>
using namespace std;
long long mod=1e9+7;
struct Matrix
{
    int m[4][4];//因为本题是斐波那契数列,只需要用到2,所以开4(无所谓)
};//定义矩阵结构体
Matrix ans,res,a;

Matrix Mul(Matrix A,Matrix B)
{
    Matrix tmp;
    for(int i=1;i<=2;i++)
        for(int j=1;j<=2;j++)
            tmp.m[i][j]=0;//全部初始化为0,因为矩阵乘法是要累加的
    for(int i=1;i<=2;i++)
        for(int j=1;j<=2;j++)
            for(int k=1;k<=2;k++)
                tmp.m[i][j]+=A.m[i][k]*B.m[k][j];//矩阵乘法定义
    return tmp; 
}
 
void quickpower(int N)
{
    ans.m[1][1]=1;
    ans.m[1][2]=1;
    ans.m[2][1]=0;
    ans.m[2][2]=0;//这里的初始化,也是应题,斐波那契数列,前两项是1,1;而剩下的位置补0,对结果是没有影响的。这里必须要用ans去一个个乘res,而不能把res的N次方算出来再去求ans乘以res^N,因为矩阵没有交换律结合律,那样会改变结果
while(N)
{
    if(N&1)
    ans=Mul(ans,res);
    res=Mul(res,res);
    N=N>>1;
}//快速幂的另一种写法:当还有指数的时候(指数不为0):如果N%2==1(N&1),那么就需要多乘一个res矩阵;如果是偶数,那么就不用多乘。下一步是让res平方,举个例子:2^9=2*2*2*2*2*2*2*2*2,那么ans=2,剩下的是8个2相乘,就相当于4个2^2相乘,所以指数除以2(N>>1),res平方

}
int main()
{
    long long x;
    cin>>x;
    if(x==1||x==2)
    {
        cout<<1;
        return 0;
    }//如果是前两个,直接输出
    res.m[1][1]=1;
    res.m[1][2]=1;
    res.m[2][1]=1;
    res.m[2][2]=0;//不多解释,凑出这个,和Fn,Fn-1相乘可以得出Fn+1,Fn
    quickpower(x-2);//从第三项开始运用公式,所以减2.比如说F3,是F2乘一次res得来的,所以是x-2
    cout<<ans.m[1][1]%mod;//输出结果
}

 

 

对于刚学矩阵快速幂的蒟蒻我来说,真的太难了,而且写了半天最后才40。还需要改进啊!

 

6.17:

三个月之后,我回来了,我终究是学会了矩阵快速幂优化斐波那契数列(而且我还学会了插入代码。。。Ctrl C+Ctrl V实在是太low了,而且代码没有缩进)

思路:没有可说的,用初始的数组乘以中间构造的矩阵,运用矩阵乘法的结合律,配合快速幂可以大大降低时间复杂度。

代码:

 

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
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));//每次做乘法之前要清空tmp数组,这个矩阵只是中间存数据用的,最后都会存到res矩阵里面 。因为矩阵乘法运算过程中要用到本来的res矩阵里的数据,所以不能及时更新,不然会导致答案错误 
    for(register int i=1;i<=2;i++)
        for(register int j=1;j<=2;j++)
            for(register int k=1;k<=2;k++)//register加速 
                tmp[i][j]=(tmp[i][j]+res[i][k]*mul[k][j])%1000000007;//矩阵乘法 
    for(register int i=1;i<=2;i++)
        for(register int j=1;j<=2;j++)
            res[i][j]=tmp[i][j];//更新答案矩阵 
}
void mul_2()
{
    memset(tmp,0,sizeof(tmp));
    for(register int i=1;i<=2;i++)
        for(register int j=1;j<=2;j++)
            for(register int k=1;k<=2;k++)
                tmp[i][j]=(tmp[i][j]+mul[i][k]*mul[k][j])%1000000007;
    for(register int i=1;i<=2;i++)
        for(register int j=1;j<=2;j++)
            mul[i][j]=tmp[i][j];//这个函数跟上一个基本一样,只不过上一个函数在快速幂里是当指数为奇数时把多的那一个矩阵乘到答案矩阵里。而这个函数是让构造的矩阵平方的操作 
}
void solve()
{
    for(register int i=1;i<=2;i++)
        for(register int j=1;j<=2;j++)
            tp[i]=(tp[i]+res[i][j]*a[j])%1000000007;//将最初的[1,1]与快速幂之后的矩阵乘起来,那么就可以得到最终结果了 
    printf("%lld\n",tp[1]);//最终结果是在前面,所以输出前面的那个数 
}
int main()
{
    scanf("%lld",&n);
    if(n<=2)printf("1\n");//特判,因为前两个数必须初始化,若n<=2直接输出即可 
    else
    {
        a[1]=a[2]=1;
        for(register int i=1;i<=2;i++)
            res[i][i]=1;
        for(register int i=1;i<=2;i++)
            for(register int j=1;j<=2;j++)
                mul[i][j]=1;
        mul[2][2]=0;//构造那个可以算出斐波那契数列的矩阵 
        n-=2;//n-=2的原因是前两个数并没有乘以矩阵,所以n-=2 
        while(n)//矩阵快速幂 
        {
            if(n&1)mul_1();/*结果矩阵乘以[ 1,1]这个矩阵 
                                       [ 1,0]*/ 
            n>>=1;
            mul_2();
        }//快速幂 
        solve();
    }
    return 0;
}

 

(三个月前的我真垃圾)

难道这样就结束了吗?不可能的,我现在要亲手把我40分的代码改成AC代码。

 

代码:

 

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<algorithm>
using namespace std;
long long mod=1e9+7;
long long x;
struct Matrix
{
    long long m[4][4];//因为本题是斐波那契数列,只需要用到2,所以开4(无所谓)
};//定义矩阵结构体
Matrix ans,res;
Matrix Mul(Matrix A,Matrix B)
{
    Matrix tmp;
    for(int i=1;i<=2;i++)
        for(int j=1;j<=2;j++)
            tmp.m[i][j]=0;//全部初始化为0,因为矩阵乘法是要累加的
    for(int i=1;i<=2;i++)
        for(int j=1;j<=2;j++)
            for(int k=1;k<=2;k++)
                tmp.m[i][j]=(tmp.m[i][j]+A.m[i][k]*B.m[k][j])%mod;//矩阵乘法定义
    /*for(int i=1;i<=2;i++)
    {
        for(int j=1;j<=2;j++)
            cout<<tmp.m[i][j]<<" ";
        cout<<endl;
    }*/
    return tmp;
}
void quickpower(long long N)
{
    while(N)
    {
        if(N&1)
        ans=Mul(ans,res);
        res=Mul(res,res);
        N>>=1;
    }//快速幂的另一种写法:当还有指数的时候(指数不为0):如果N%2==1(N&1),那么就需要多乘一个res矩阵;如果是偶数,那么就不用多乘。下一步是让res平方,举个例子:2^9=2*2*2*2*2*2*2*2*2,那么ans=2,剩下的是8个2相乘,就相当于4个2^2相乘,所以指数除以2(N>>1),res平方
}
int main()
{
    cin>>x;
    if(x<=2)
    {
        cout<<1<<endl;
        return 0;
    }//如果是前两个,直接输出
    ans.m[1][1]=1;
    ans.m[1][2]=1;
    ans.m[2][1]=0;
    ans.m[2][2]=0;//这里的初始化,也是应题,斐波那契数列,前两项是1,1;而剩下的位置补0,对结果是没有影响的。这里必须要用ans去一个个乘res,而不能把res的N次方算出来再去求ans乘以res^N,因为矩阵没有交换律结合律,那样会改变结果
    res.m[1][1]=1;
    res.m[1][2]=1;
    res.m[2][1]=1;
    res.m[2][2]=0;//不多解释,凑出这个,和Fn,Fn-1相乘可以得出Fn+1,Fn
    quickpower(x-2);//从第三项开始运用公式,所以减2.比如说F3,是F2乘一次res得来的,所以是x-2
    cout<<ans.m[1][1]%mod<<endl;//输出结果
}

 

第一次的代码和第二次的代码思路不同:第一次的代码是编了一个具有普遍性的矩阵乘法(只不过为了做斐波那契数列只循环到2),所以只需要一个函数在快速幂里进行矩阵运算即可。而第二种思路是一种全局运算,而且只针对于斐波那契数列这个题。两个矩阵乘法的函数一个是为了更新答案,另一个是为了平方运算,两个互不干扰,但一直是用全局数组操作,所以可以这么玩。但是第一种就是一个局部的计算,在函数里开结构体,算完再返回那个结构体。虽然我都是抄的别人的。。。但是我现在已经完全明白了并且将自己的代码改成了AC代码。

 

我之前的代码有三个致命的问题:1.我开的long long x,但是快速幂里用的是int(脑抽)。2.结构体里面的数组也要开long long(脑抽++)。3.矩阵乘法里我没有模mod(脑抽+=2)。总的下来,三个月以前的我就是个nc。

但  还  是  成  就  感  u  p 

posted @ 2020-06-17 17:30  徐明拯  阅读(377)  评论(0)    收藏  举报