快速求斐波那契数列第n项(不使用矩阵快速幂)——杨子曰数学?题目?

快速求斐波那契数列第n项(不使用矩阵快速幂)——杨子曰数学?题目?

超链接:数论合集


就是说让你在O(log n)的时间里告诉你斐波那契数列第n项是谁

然而,我们不使用矩阵快速幂这种难理解的东西

我们要使用一种更加高级,更好理解的东西——斐波那契数列二倍项公式


咱们先上公式:
f 2 n = f n ∗ ( f n − 1 + f n + 1 ) f_{2n}=f_n*(f_{n-1}+f_{n+1}) f2n=fn(fn1+fn+1)

然后来一个nb的证明:

众所周知,斐波那契数列的第n项是可以表示:我爬楼梯,开始时站在第1级台阶上,可以一步跨一级,或者一步跨两级的,爬到第n级的爬法总数

上面这个很好理解,想要爬到第n级,我们可以从第n-2级爬两级上来,也可以从n-1级跨一级上来,那么通过加法原理我们得到了: f n = f n − 2 + f n − 1 f_n=f_{n-2}+f_{n-1} fn=fn2+fn1,也就是斐波那契数列

知道了这一点以后,我们回归正题,现在我们要求 f 2 n f_{2n} f2n,也就是我们要爬到第2n级台阶,现在我们把第n级台阶视为一个中转站来考虑这个问题

  1. 如果不踩到第n级台阶,也就是说我直接从n-1级台阶跨了两级道了第n+1级台阶,那么爬完前n-1级台阶有f[n-1]种选法,然后n-1,n,n+1这3级的情况已经被我们定下来了,就不用考虑,现在我们必定在n+1级台阶上,然后我们还要爬到第2n级台阶,也就是说我们有 f n f_{n} fn种选择,前n-1级台阶和后n级台阶显然是先后的关系,So,我们使用乘法原理,得到:当我们不踩到第n级台阶时,爬到第2n级台阶,有 f n − 1 ∗ f n f_{n-1}*f_{n} fn1fn种选法
  2. 如果踩到第n级台阶,到第n级台阶,我们有n种选法,现在我们站在第n级台阶上,然后要爬到第2n级台阶,也就是说我们还要有 f n + 1 f_{n+1} fn+1种选择,依然是乘法原理,我们得到了:当我们踩到第n级台阶时,爬到第2n级台阶,有 f n ∗ f n + 1 f_{n}*f_{n+1} fnfn+1种选法

显然上面两种情况是并列的,So,我们使用加法原理,得到最终答案:
f 2 n = f n − 1 ∗ f n + f n ∗ f n + 1 f_{2n}=f_{n-1}*f_n+f_n*f_{n+1} f2n=fn1fn+fnfn+1
f 2 n = f n ∗ ( f n − 1 + f n + 1 ) f_{2n}=f_n*(f_{n-1}+f_{n+1}) f2n=fn(fn1+fn+1)

得证

BUT,你有没有发现这个东西有个BUG,它只能算偶数项,不能算奇数项,所以我们还要来推一推:

首先,我们从 f 2 n + 2 = f 2 n + 1 + f 2 n f_{2n+2}=f_{2n+1}+f_{2n} f2n+2=f2n+1+f2n开始,移个项,我们得到了:
f 2 n + 1 = f 2 n + 2 − f 2 n f_{2n+1}=f_{2n+2}-f_{2n} f2n+1=f2n+2f2n
哦,有没有发现右边两个都是偶数项,那么用我们上面推出的公式代掉,得到了:
f 2 n + 1 = f n + 1 ∗ ( f n + f n + 2 ) − f n ∗ ( f n − 1 + f n + 1 ) f_{2n+1}=f_{n+1}*(f_n+f_{n+2})-f_n*(f_{n-1}+f_{n+1}) f2n+1=fn+1(fn+fn+2)fn(fn1+fn+1)
虽然右边的下标已经没有系数2了,但是由于太丑了,我们继续往下化简,把括号拆开:
f 2 n + 1 = f n + 1 ∗ f n + f n + 1 ∗ f n + 2 − f n ∗ f n − 1 − f n ∗ f n + 1 f_{2n+1}=f_{n+1}*f_n+f_{n+1}*f_{n+2}-f_n*f_{n-1}-f_n*f_{n+1} f2n+1=fn+1fn+fn+1fn+2fnfn1fnfn+1
有没有发现第一项和第四项可以抵消掉:
f 2 n + 1 = f n + 1 ∗ f n + 2 − f n ∗ f n − 1 f_{2n+1}=f_{n+1}*f_{n+2}-f_n*f_{n-1} f2n+1=fn+1fn+2fnfn1
这个式子还是有一个缺点算一个 f 2 n + 1 f_{2n+1} f2n+1得先算出四项,会拉低程序的效率,So,我们继续化简:
f 2 n + 1 = f n + 1 ∗ ( f n + f n + 1 ) − f n ∗ ( f n + 1 − f n ) f_{2n+1}=f_{n+1}*(f_n+f_{n+1})-f_n*(f_{n+1}-f_n) f2n+1=fn+1(fn+fn+1)fn(fn+1fn)
f 2 n + 1 = f n + 1 ∗ f n + ( f n + 1 ) 2 − f n ∗ f n + 1 + f n 2 f_{2n+1}=f_{n+1}*f_n+(f_{n+1})^2-f_n*f_{n+1}+{f_n}^2 f2n+1=fn+1fn+(fn+1)2fnfn+1+fn2
这样一来:
f 2 n + 1 = ( f n + 1 ) 2 + f n 2 f_{2n+1}=(f_{n+1})^2+{f_n}^2 f2n+1=(fn+1)2+fn2
多么优美的公式啊!


再给大家一个相对理性一点的证明(从我这篇文章发现的):

首先我们假设 n < m , f [ n ] = a , f [ n + 1 ] = b n<m,f[n]=a,f[n+1]=b n<m,f[n]=a,f[n+1]=b
这样一来我们往后推一下发现:
f [ n + 1 ] = a + b , f [ n + 2 ] = a + 2 b , f [ n + 3 ] = 2 a + 3 b ⋯ ⋯ f [ m ] = f [ m − n − 1 ] a + f [ m − n ] b f[n+1]=a+b,f[n+2]=a+2b,f[n+3]=2a+3b \cdots \cdots f[m]=f[m−n−1]a+f[m−n]b f[n+1]=a+b,f[n+2]=a+2b,f[n+3]=2a+3bf[m]=f[mn1]a+f[mn]b

然后我们把m分别用2n和2n+1代掉也同样可以得到上面的结论


然后我们就可以再O(log n)的时间了递归求出 f n f_n fn了,别忘了要把当前求过的值记录一下,就有点类似记忆化搜索,可以使用map(好东西)

OK,完事


c++代码(洛谷P1962):

#include
#define ll long long
#define p 1000000007
using namespace std;

ll n;

map<ll,ll> mp;

ll calc(ll n){
	if (mp[n]) return mp[n];
	ll tmp=n;n/=2;
	if (tmp%2) return mp[tmp]=calc(n)*calc(n)%p+calc(n+1)*calc(n+1)%p;
	else return mp[tmp]=(2*calc(n-1)%p+calc(n)%p)*calc(n)%p;
}

int main(){
	scanf("%lld",&n);
	mp[1]=1;
	mp[2]=1;
	cout<<calc(n)%p;
	return 0;
} 

参考:
https://www.zhihu.com/question/49642985/answer/329341469?edition=yidianzixun&utm_source=yidianzixun&yidian_docid=K_006PAjUf&yidian_s=9&yidian_appid=

https://chhokmah.blog.luogu.org/solution-p1962

于HG机房

你可能感兴趣的:(崩溃的数学,恶心的题目)