快速求斐波那契数列第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∗(fn−1+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=fn−2+fn−1,也就是斐波那契数列
知道了这一点以后,我们回归正题,现在我们要求 f 2 n f_{2n} f2n,也就是我们要爬到第2n级台阶,现在我们把第n级台阶视为一个中转站来考虑这个问题
- 如果不踩到第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} fn−1∗fn种选法
- 如果踩到第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} fn∗fn+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=fn−1∗fn+fn∗fn+1
f
2
n
=
f
n
∗
(
f
n
−
1
+
f
n
+
1
)
f_{2n}=f_n*(f_{n-1}+f_{n+1})
f2n=fn∗(fn−1+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+2−f2n
哦,有没有发现右边两个都是偶数项,那么用我们上面推出的公式代掉,得到了:
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∗(fn−1+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+1∗fn+fn+1∗fn+2−fn∗fn−1−fn∗fn+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+1∗fn+2−fn∗fn−1
这个式子还是有一个缺点算一个
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+1−fn)
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+1∗fn+(fn+1)2−fn∗fn+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+3b⋯⋯f[m]=f[m−n−1]a+f[m−n]b
然后我们把m分别用2n和2n+1代掉也同样可以得到上面的结论
然后我们就可以再O(log n)的时间了递归求出 f n f_n fn了,别忘了要把当前求过的值记录一下,就有点类似记忆化搜索,可以使用map(好东西)
OK,完事
c++代码(洛谷P1962):
#include<bits/stdc++.h>
#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机房