[算法系列]数学01-斐波那契数列

243 阅读1分钟

斐波那契数列

定义:

F1=1,F2=1Fn=Fn1+Fn2n2设F_{1}=1,F_{2}=1\\ F_{n}=F_{n-1}+F_{n-2}\quad n\ge2\\

满足上面的通项公式的数列就叫斐波那契数列

通项公式:

斐波那契通项公式的求法有很多,高中阶段可以用特征根法解决,我们介绍矩阵相似对角化的方法:

首先,我们先根据通项公式构造出下面的方程:

(FnFn1)=[1110](Fn1Fn2)n3\binom{F_{n}}{F_{n-1}}=\begin{bmatrix} 1 & 1\\ 1 &0 \end{bmatrix}\binom{F_{n-1}}{F_{n-2}}\quad n\ge3

令:

fn=(FnFn1)n2A=[1110]f_{n}=\binom{F_{n}}{F_{n-1}}\quad n\ge2\\ A=\begin{bmatrix} 1 & 1\\ 1 &0 \end{bmatrix}

我们规定:

f1=(11)f_{1}=\binom{1}{1}

我们可以得到:

fn=An2f1f_{n}=A^{n-2}f_1

要解决矩阵的幂的问题,我们自然而然地想到相似对角化

我们先求出A的特征值:

λ1=5+12λ2=512\lambda_{1}=\frac{\sqrt{5}+1}{2}\quad\lambda_{2}=\frac{\sqrt{5}-1}{2}

解出特征向量:

α1=(5+121)α2=(5121)\alpha_{1}=\binom{\frac{\sqrt{5}+1}{2}}{1}\quad\alpha_{2}=\binom{\frac{\sqrt{5}-1}{2}}{1}

令:

P=(α1,α2)P=(\alpha_1,\alpha_2)

得到:

A=PQP1其中QA对应的对角矩阵A=PQP^{-1}\\ 其中Q为A对应的对角矩阵

于是:

An2=P[λ1n200λ2n2]P1A^{n-2}=P\begin{bmatrix} \lambda_1^{n-2} & 0\\ 0 &\lambda_2^{n-2} \end{bmatrix}P^{-1}

利用λ1+λ2=1,可以计算出上式,得到通项公式为:

Fn=15[(5+12)n(512)n]F_{n}=\frac{1}{\sqrt{5}}[(\frac{\sqrt{5}+1}{2})^{n}-(\frac{\sqrt{5}-1}{2})^{n}]

前n项和相关的结论:

我们写出下面一系列式子:

F3=F2+F1F4=F3+F2...Fn+2=Fn+1+FnF_3=F_2+F_1\\ F_4=F_3+F_2\\ ...\\ F_{n+2}=F_{n+1}+F_{n}

累加得:

Sn+22=Sn+11+SnS_{n+2}-2=S_{n+1}-1+S_n

移项整理得:

Sn=Fn+21S_n=F_{n+2}-1

可见,想要求斐波那契数列的和,归根结底还是求出相应的项

矩阵快速幂

我们可以实现一个矩阵快速幂,来快速地求出对应的斐波那契数列

我们先来实现矩阵乘法运算:

void matmul(int c[][2],int a[][2],int b[][2]){
    int temp[2][2]={0};
    for(int i=0;i<2;i++){
        for(int j=0;j<2;j++){
            for(int k=0;k<2;k++){
                temp[i][j]=(temp[i][j]+a[i][k]*b[k][j]);
            }
        }
    }
    memcpy(c,temp,sizeof temp);
}

再仿照快速幂的方法写出矩阵快速幂:

void matpow(int r[][2],int a[][2],int p){
    int temp[2][2]={
            {1,0},
            {0,1}
    };
    while(p){
        if(p&1)matmul(temp,temp,a);
        matmul(a,a,a);
        p>>=1;
    }
    memcpy(r,temp,sizeof temp);
}

如此,我们就有了计算斐波那契数列的利器!

小试牛刀:

例题:

image.png

AC代码:

#include<iostream>
#include<cstring>
using namespace std;
typedef long long ll;
int n,m;
void matmul(int c[][2],int a[][2],int b[][2]){
    int temp[2][2]={0};
    for(int i=0;i<2;i++){
        for(int j=0;j<2;j++){
            for(int k=0;k<2;k++){
                temp[i][j]=(temp[i][j]+(ll)a[i][k]*b[k][j])%m;
            }
        }
    }
    memcpy(c,temp,sizeof temp);
}
void matpow(int r[][2],int a[][2],int p){
    int temp[2][2]={
            {1,0},
            {0,1}
    };
    while(p){
        if(p&1)matmul(temp,temp,a);
        matmul(a,a,a);
        p>>=1;
    }
    memcpy(r,temp,sizeof temp);
}
int main(){
    cin>>n>>m;
    if(m==1){
        cout<<"0"<<endl;
        return 0;
    }
    int a[2][2]={
            {1,1},
            {1,0}
    };
    matpow(a,a,n);
    cout<<(a[0][0]+a[0][1])%m-1<<endl;
    return 0;
}