前言:
1、本文主要介绍了如何对斐波拉契数列的高项进行求解,省略了对于朴素求解算法的详细介绍。
2、本文对于斐波拉契数列的前缀和以及多次求前缀和的方法进行一定的分析,得出了转移矩阵的一般形式。
3、编写公式开始是在AxMath进行的书写,写完以后发现好像不能直接将原文本导出(只有LaTex文本文件),故下面只是将导出的图片进行了展示,带来不变请谅解。
4、最后进行了代码的实现。
(一)斐波拉契数列求解
(二)斐波拉契数列前缀和
(三)斐波拉契数列m阶前缀和
(四)代码实现
1、斐波拉契数列的求解:
/**
支持1e18范围内的斐波拉契数列值的求解
MOD为取模以后的结果
这里模数较大,需要用到模拟乘法或者O(1)快速乘
但是O(1)快速乘可能会有精度误差(当模数较大)
当MOD*2会爆long long时这里的乘法MULL会出错,需要重写
**/
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#define ll long long
#define MOD 1000000000000000000 //模数1e18
#define FOR(i,a,b) for(int i=a;i<b;i++)
using namespace std;
inline ll ksc(ll x,ll y,ll mod){ //O(1)快速乘?
return (x*y-(ll)((long double)x/mod*y)*mod+mod)%mod;
}
inline ll mul_mod_ll(ll a,ll b,ll mod){ //O(1)快速乘?
ll d=(ll)floor(a*(double)b/mod+0.5);
ll ret=a*b-d*mod;
if(ret<0)ret+=mod;
return ret;
}
ll MUll(ll x,ll y,ll mod){ //O(log)快速乘?
ll ans=0;
while(y){
if(y&1)ans=(ans+x)%mod;
y>>=1;x=(x<<1)%mod;
}
return ans;
}
void MUL(ll A[2][2],ll B[2][2],ll mod){ //矩阵乘法
ll C[2][2]={0,0,0,0};
FOR(i,0,2)FOR(j,0,2)FOR(k,0,2)
C[i][j]=(C[i][j]+MUll(A[i][k],B[k][j],mod))%mod;
memcpy(A,C,sizeof C);
}
ll fib(ll a,ll b,ll n,ll mod){ //快速幂
ll X[2][2]={1,1,1,0};
ll E[2][2]={1,0,0,1};
while(n){
if(n&1)MUL(E,X,mod);
n>>=1;MUL(X,X,mod);
}
return b*E[0][0]+a*E[0][1];
}
int main(){
ll n;
ll a=1,b=1; //f(0)、f(1)
while(cin>>n){
printf("%lld\n",n<0?-1:n==0?a:n==1?b:fib(a,b,n-1,MOD));
}
return 0;
}
2、斐波拉契数列m阶前缀和:
#include<iostream>
#include<cstring>
#include<string>
#include<cstdio>
#include<algorithm>
#define MOD 1000000009 //注意模数的大小
#define ll long long
using namespace std;
ll E[110][110],e[110][110];
ll X[110][110],x[110][110],l[110],a,b,c,d,t;
void init(){ //初始化单位矩阵
for(int i=0;i<105;i++){
e[i][i]=1;
for(int j=0;j<=i;j++)x[i][j]=1;
}
x[0][0]=0;x[0][1]=1;
}
void MUL(ll m1[110][110],ll m2[110][110]){ //矩阵乘法
ll m3[110][110];
memset(m3,0,sizeof m3);
for(int i=0;i<=c;i++)
for(int j=0;j<=c;j++)
for(int k=0;k<=c;k++)
m3[i][j]=(m3[i][j]+m1[i][k]*m2[k][j])%MOD;
memcpy(m1,m3,sizeof m3);
}
ll ksm(ll n){ //快速幂
memcpy(E,e,sizeof e);
memcpy(X,x,sizeof x);
while(n){
if(n&1)MUL(E,X);
n>>=1;MUL(X,X);
}
ll ans=0;
for(int i=c;i>=0;i--)
ans=(ans+l[i]*E[c][i])%MOD;
return ans;
}
int main(){
// freopen("in.txt","r",stdin);
init();
scanf("%d",&t);
while(t--){
scanf("%d%d%d%d",&a,&b,&c,&d); //a,b分别为f(0),f(1)
//c,d表示c阶的前d项和
if(d==0){printf("%I64d\n",a);} //第一列全为a,d从0开始
else{
l[0]=a%MOD;c++; //初始化第二列
for(int i=1;i<=c;i++)
l[i]=((ll)(i-1)*a+b)%MOD;
printf("%I64d\n",ksm(d-1)); //快速幂--返回c阶的S(d)值
//c==0时返回的就是斐波拉契数列的f(d)值
}
}
return 0;
}
(五)总结
1、对于上述介绍的方法复杂度都是O(m^3logn)的,当m较大时性能将变得极差,这里主要解决的是在m较小时对于高项的单次(或少数次)求解。
2、对于上述编写的代码可能会有一定的错误,若有纰漏,烦请斧正。
3、实际上对于m阶前缀和的求解,我们将f(0)=a,f(1)=b的值分开加,可以得到最终a与b的系数的规律,可以在更短的时间内求得(万能的学长)。