关于斐波那契数列

FZQOJ 博客链接

博客园链接

关于斐波那契数列

警告:本文使用了微少的 \(\LaTeX\),请谨慎食用。

斐波那契数列的定义

\[F(n)=\begin{cases}0&n=0\\1&n=1\\F(n-1)+F(n-2)&n \ge 2\end{cases} \]

这个是很显然的吧。

例题

P1720 月落乌啼算钱(斐波那契数列)

这一道题是语法的入门题,由于项数不超过 \(33\),我们可以直接使用递推公式。

#include<cstdio>
int main(){
	int n,n1=1,n2=1,s=0;//第一项和第二项。
	scanf("%d",&n);
	if (n==1) return printf("%d\n",n1),0;
	s=n2;
	for (int i=3;i<n;i++){
		if (i&1) n1+=n2,s=n1;
		else n2+=n1,s=n2;
	} 
	printf("%d\n",s);
    return 0;
}

上面的代码时间复杂度是 \(O(n)\) 的,但是,斐波那契数列是有公式的!我们不能只止步于递推。

公式:

\[F(n)=\dfrac{1}{\sqrt5}\left[(\dfrac{1+\sqrt5}{2})^n-(\dfrac{1-\sqrt5}{2})^n\right] \]

证明(虽然我也没看懂):

斐波那契通项公式证明.png

于是我们就可以利用上面的通项公式,使用 cmath 库中的 sqrtpow 函数来简洁地求解:

//the code is from chenjh
#include<cstdio>
#include<cmath>
int main(){
	int n;scanf("%d",&n);
	printf("%.2lf\n",1.0/sqrt(5)*(pow((1+sqrt(5))/2,n)-pow((1-sqrt(5))/2,n)));
	return 0;
}

上面的代码的时间复杂度是 \(O(1)\) 的!(我们可以暂且认为 sqrtpow 函数的时间复杂度 \(O(1)\) 的)

「POJ3070」Fibonacci

此题要求 \(F_n (0 \le n \le 10^9)\),普通的 \(O(n)\) 递推一定是无法胜任的。

所以我们就需要——矩阵乘法来加速递推。

斐波那契数列的另外一个通项公式是:

\[\begin{bmatrix}F_{n+1}&F_n\\F_n&F_{n-1}\end{bmatrix}=\begin{bmatrix}1&1\\1&0\end{bmatrix}^n=\begin{matrix}\underbrace{\begin{bmatrix}1&1\\1&0\end{bmatrix}\begin{bmatrix}1&1\\1&0\end{bmatrix}\dots\begin{bmatrix}1&1\\1&0\end{bmatrix}}\\{n \text{ times}}\end{matrix} \]

于是我们就可以利用矩阵乘法配合快速幂进行 \(O(2^3 \log n)\) 复杂度的递推,其中 \(2^3\) 为矩阵乘法所需要的复杂度。

//the code is from chenjh
#include<iostream>
#include<cstring>
using namespace std;
typedef long long LL;
const int m=10000;
void mul(int f[2],int a[2][2]){
	int c[2];
	memset(c,0,sizeof(c));
	for(int j=0;j<2;j++)
		for(int k=0;k<2;k++)
			c[j]=(c[j]+(LL)f[k]*a[k][j])%m;
	memcpy(f,c,sizeof(c));
}
void mulself(int a[2][2]){
	int c[2][2];
	memset(c,0,sizeof(c));
	for(int i=0;i<2;i++)
		for(int j=0;j<2;j++)
			for(int k=0;k<2;k++)
				c[i][j]=(c[i][j]+(LL)a[i][k]*a[k][j])%m;
	memcpy(a,c,sizeof(c));
}
int main(){
	int n;
	while(cin>>n && n!=-1){
		int f[2]={0,1};
		int a[2][2]={{0,1},{1,1}};
		for(;n;n>>=1){
			if(n&1) mul(f,a);
			mulself(a);
		}
		cout<<f[0]<<endl;
	}
	return 0;
}

LibreOJ #10221. 「一本通 6.5 例 3」Fibonacci 前 n 项和

设斐波那契数列的第 \(n\) 项为 \(F_n\),前 \(n\) 项和为 \(S_n\)

方法一

根据矩阵乘法的定义,可以列出公式:

\[\begin{bmatrix}S_n&F_n&F_{n+1}\end{bmatrix}=\begin{bmatrix}S_{n-1}&F_{n-1}&F_n\end{bmatrix} \times \begin{bmatrix}1&0&0\\0&0&1\\1&1&1\end{bmatrix} \]

最后利用矩阵乘法配合快速幂实现,时间复杂度为 \(O(3^3 \log n)\)

//the code is from chenjh
#include<cstdio>
#include<cstring>
typedef long long LL;
LL m;
void mul(LL f[3],LL a[3][3]){
	LL c[3];
	memset(c,0,sizeof(c));
	for(int j=0;j<3;j++)
		for(int k=0;k<3;k++)
			c[j]=(c[j]+f[k]*a[k][j])%m;
	memcpy(f,c,sizeof(c));
}
void mulself(LL a[3][3]){
	LL c[3][3];
	memset(c,0,sizeof(c));
	for(int i=0;i<3;i++)
		for(int j=0;j<3;j++)
			for(int k=0;k<3;k++)
				c[i][j]=(c[i][j]+a[i][k]*a[k][j])%m;
	memcpy(a,c,sizeof(c));
}
int main(){
	LL n;
	scanf("%lld%lld",&n,&m);
	LL f[3]={0,0,1};
	LL a[3][3]={{1,0,0},{0,0,1},{1,1,1}};
	for(;n;n>>=1){
		if(n&1) mul(f,a);
		mulself(a);
	}
	printf("%lld\n",f[0]);
	return 0;
}

方法二

公式:

\[S_n=F_{n+2}-1 \]

证明:

运用数学归纳法。

\(n=1\) 时,命题成立。

假设 \(n=k\) 时,命题成立。

\(n=k+1\) 时:

\[\begin{aligned}F_{k+3}-1 &= F_{k+1}+F_{k+2}-1\\&= F_{k+1}+\sum\limits_{i=1}^kF_i\\&=\sum\limits_{i=1}^{k+1}F_i\end{aligned} \]

所以直接用矩阵乘法配合快速幂求出 \(F_{n+2}\) 即可。

时间复杂度 \(O(2^3 \log n)\)

//the code is from chenjh
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
LL m;
void mul(LL f[2],LL a[2][2]){
	LL c[2];
	memset(c,0,sizeof(c));
	for(int j=0;j<2;j++)
		for(int k=0;k<2;k++)
			c[j]=(c[j]+f[k]*a[k][j])%m;
	memcpy(f,c,sizeof(c));
}
void mulself(LL a[2][2]){
	LL c[2][2];
	memset(c,0,sizeof(c));
	for(int i=0;i<2;i++)
		for(int j=0;j<2;j++)
			for(int k=0;k<2;k++)
				c[i][j]=(c[i][j]+a[i][k]*a[k][j])%m;
	memcpy(a,c,sizeof(c));
}
int main(){
	LL n;
	scanf("%lld%lld",&n,&m);
	n+=2;
	LL f[2]={0,1};
	LL a[2][2]={{0,1},{1,1}};
	for(;n;n>>=1){
		if(n&1) mul(f,a);
		mulself(a);
	}
	printf("%lld\n",f[0]-1);
	return 0;
}

LibreOJ #10222. 「一本通 6.5 例 4」佳佳的 Fibonacci

计算出前 \(n\) 项,输入到 OEIS 中,你会发现就是 A014286 号数列。

通项公式是:

\[S(n)=n \times F_{n+2} - F_{n+3} +2 \]

于是就可以直接计算了,但是我们仍要证明一下:

把他变成一个三角形:

\[\begin{matrix}F_1&&&&\\F_2&F_2&&&\\F_3&F_3&F_3&&&\\F_4&F_4&F_4&F_4&&\\F_5&F_5&F_5&F_5&F_5\end{matrix} \]

\[\dots \]

答案就是这个三角形内所有数之和。

我们可以把这个三角形竖着看,发现了啥?这个式子就是:

\[\sum \limits_{i=1}^n (S_n-S_{i-1}) \]

\(S_n\) 提取出来:

\[nS_n-\sum\limits_{i=1}^nS_{i-1} \]

这个时候,\(S_n=F_{n+2}-1\) 派上用场了。把这个式子往里面套啊!

\[n(F_{n+2}-1) - \sum\limits_{i=1}^n (F_{i+1}-1) \]

发现了啥?\(\sum\limits_{i=1}^n (F_{i+1}-1)\) 其实也可以转化为 \(S_{n+1}-F_1-n\)

\(S_n=F_{n+2}-1\) 再往 \(S_{n+1}\) 里面套!

答案就变成了:

\[n(F_{n+2}-1)- (F_{n+3}-1-F_1-n) =nF_{n+2}-n-F_{n+3}+1+F_1+n =nF_{n+2}-F_{n+3}+2 \]

//the code is from chenjh
#include<cstdio>
#include<cstring>
#define rep(i,a,b) for(int i=(a);i<(b);++i)
typedef long long LL;
int n,m;
void mul(int f[2],int a[2][2]){
	int c[2];
	memset(c,0,sizeof c);
	rep(i,0,2)rep(j,0,2)c[i]=(c[i]+(LL)f[j]*a[j][i])%m;
	memcpy(f,c,sizeof c);
}
void mulself(int a[2][2]){
	int c[2][2];
	memset(c,0,sizeof c);
	rep(i,0,2)rep(j,0,2)rep(k,0,2)c[i][j]=(c[i][j]+(LL)a[i][k]*a[k][j])%m;
	memcpy(a,c,sizeof c);
}
int main(){
	scanf("%d%d",&n,&m);
	int f[2]={0,1};
	int a[2][2]={{0,1},{1,1}};
	for(int x=n+2;x;x>>=1,mulself(a))if(x&1)mul(f,a);
	printf("%lld\n",((LL)n*f[0]-f[1]+2+m)%m);
	return 0;
}

补充知识

  1. fib在模m后,一定存在周期性。
  2. More:斐波那契数列的性质 - Milkor - 博客园 (cnblogs.com)

参考文献

本人制作不易,公式可能会有打错的地方,欢迎指出错误,点个赞吧

posted @ 2023-04-16 20:20  Chen_Jinhui  阅读(38)  评论(0)    收藏  举报