一段简短的计算Fibonacci数列的代码

今天听人民日报说算fibonacci数列的是顶尖科学家,我也来算一算。

  1. int fib(ll n){
  2. 	ll x=0,y=1,t;
  3. 	for (int d=62-__builtin_clz(++n);d>=0;d--){
  4. 		t=(x*x+y*y)%P; y=(x+x+y)*y%P; x=t;
  5. 		if (n>>d&1) x=y,y=(t+y)%P;
  6. 	}
  7. 	return x;
  8. }

ccsp一道题比赛卡常数算fibonacci,非常难受。

矩阵快速幂谁都会,慢得一匹。当然再暴力三重for循环的时候也记要把阶数写个常数2,不然-O2编译器没法循环展开。一个有效的观察是,发现这个矩阵的幂反对角线上

然后我们知道,计算这种根式$$\frac{1}{\sqrt{5}} ((\frac{\sqrt{5}+1}{2})^n+ (\frac{\sqrt{5}-1}{2})^n )$$的时候,是可以对根式做快速幂的,非常快,比矩阵快5~10倍 (模数固定的话,可以先尝试一下二次剩余,能的话更快) 。美中不足的是,计算这个需要逆元,对模数有要求。

然后就看看怎么得到开头的代码。

几个恒等式:

F[n+m]=F[n-1]F[m]+F[n]F[m+1]
F[2n+1]=[F[n]]^2+[F[n+1]]^2
F[2n]=F[n+1]^2-F[n-1]^2=(2*F[n-1]+F[n])*F[n]

第一条可由数学归纳法得到,然后可推出后面两条。

有了后面两条,可直接递归,set存之前算出来的值记忆化,写起来比矩阵简短一点。但是set比较慢,时间复杂度和实际速度可能不如矩阵。

注意到上述计算有一个特点,就是相邻两个数n,n+1可推出2n,2n+1的值。因此递归的时候每次除以2,就可以只用保留两个数,不需要set。分奇偶讨论。

  1. pair<int,int> f(int n){
  2. 	if (n==0) {return {0,1};}
  3. 	if (n==1) {cout<<(n&1); return {1,1};}
  4. 	auto r=f(n-1>>1);
  5. 	int a=(1ll*r.first*r.first+1ll*r.second*r.second)%P;
  6. 	int b=(2ll*r.first+r.second)*r.second%P;
  7. 	cout<<(n&1); //观察什么时候为奇数
  8. 	if (n&1) return {a,b};
  9. 	else return {b,(a+b)%P};
  10. }

但是这个递归写起来比较长,看着不爽。想办法再优化一下,改成直接递推。

注意到递归按n分奇偶讨论。递推如果不能直接知道各个位的奇偶情况,那还是要栈,还不如写成递归。还好这里每次是n-1>>1,比较简单,可以找规律。n-1>>1相当于偶数的时候-1再右移一位,奇数则直接右移。因此连续的0会被取反成1,它之前的一个1会变成0,然后连锁再导致前面的连续1变0,所以效果总结起来就是取反。不放心就再打表测一下~(n+1)与上面是不是相等。

  1. int f1(int n){
  2. 	int x=0,y=1,d=0,n1=n;
  3. 	while (n1>>=1) d++;
  4. 	n1=~(n+1);
  5. 	if ((n1>>d&1)) x=1;
  6. 	d--;
  7. 	for (;d>=0;d--){
  8. 		cout<<(n1>>d&1);
  9. 		int tx=(1ll*x*x+1ll*y*y)%P;
  10. 		int ty=(2ll*x+y)*y%P;
  11. 		if ((n1>>d)&1) x=tx,y=ty;
  12. 		else x=ty,y=(tx+ty)%P;
  13. 	}
  14. 	return x;
  15. }

很好,对上了。最后工作就是对f1化简一下,就是上面的代码了。计算量和根式快速幂差不多,优点是不限制模数。

完整的测试代码:

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3.  
  4. const int P=1000000007;
  5.  
  6. pair<int,int> f(int n){
  7. 	if (n==0) {return {0,1};}
  8. 	if (n==1) {cout<<(n&1); return {1,1};}
  9. 	auto r=f(n-1>>1);
  10. 	int a=(1ll*r.first*r.first+1ll*r.second*r.second)%P;
  11. 	int b=(2ll*r.first+r.second)*r.second%P;
  12. 	cout<<(n&1);
  13. 	if (n&1) return {a,b};
  14. 	else return {b,(a+b)%P};
  15. }
  16.  
  17. int f1(int n){
  18. 	int x=0,y=1,d=0,n1=n;
  19. 	while (n1>>=1) d++;
  20. 	n1=~(n+1);
  21. 	if ((n1>>d&1)) x=1;
  22. 	d--;
  23. 	for (;d>=0;d--){
  24. 		cout<<(n1>>d&1);
  25. 		int tx=(1ll*x*x+1ll*y*y)%P;
  26. 		int ty=(2ll*x+y)*y%P;
  27. 		if ((n1>>d)&1) x=tx,y=ty;
  28. 		else x=ty,y=(tx+ty)%P;
  29. 	}
  30. 	return x;
  31. }
  32. typedef long long ll;
  33.  
  34. int f2(ll n){
  35. 	ll x=0,y=1,t;
  36. 	for (int d=62-__builtin_clz(++n);d>=0;d--){
  37. 		t=(x*x+y*y)%P; y=(x+x+y)*y%P; x=t;
  38. 		if (n>>d&1) x=y,y=(t+y)%P;
  39. 	}
  40. 	return x;
  41. }
  42.  
  43. int main(){
  44. 	int n=100;
  45. 	for (int i=0;i<=n;i++){
  46. 		cout<<(bitset<10>(i))<<' '<<(bitset<10>(~(i+1)))<<' ';
  47. 		int r=f(i).first; cout<<' ';
  48. 		int r2=f2(i);cout<<'\n';
  49. 		cout<<r<<' '<<r2<<'\n';
  50. 	}
  51.     return 0;
  52. }

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注