斐波那契数列(Fibonacci sequence),又称黄金分割数列,因意大利数学家莱昂纳多·斐波那契(Leonardo Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”。
斐波那契数列指的是这样一个数列:1,1,2,3,5,8,13,21,34,55,89..,这个数列从第3项开始,每一项都等于前两项之和。
在数学上,斐波那契数列以如下递推的方法定义:
F(0)=0,F(1)=1, F(n)=F(n - 1)+F(n - 2)(n ≥ 2,n ∈ N*)。
其通项公式为F(n)=
当所求的n值较大时,可以构造一个矩阵,利用矩阵乘法完成斐波那契数列递推的运算。如下所示。
【例1】 斐波那契数列
问题描述
斐波那契数列是指这样的数列:数列的第一个和第二个数都为1,接下来每个数都等于前面2个数之和。
给出一个正整数a,要求菲波那契数列中第a个数是多少。
输入
每个测试用例由单行中的一个整数n组成,其中0≤n≤50.输入以-1终止。
输出
每行输出对应一个输入。输出应是一个正整数,为菲波那契数列中第n个数的大小。
输入样例
3
4
5
-1
输出样例
2
3
5
(1)编程思路。
由于题目只涉及到斐波那契数列的前50项,因此可以定义一个数组long long f[51],直接采用递推的办法将数列前50项的值求出来并保存。
(2)源程序。
#include <stdio.h> int main() { long long f[51]={0,1}; for (int i=2;i<=50;i++) f[i]=f[i-1]+f[i-2]; int n; while (scanf("%d",&n) && n!=-1) { printf("%lldn",f[n]); } return 0; }
将上面的源程序提价给HDU 题库 HDU 2070 Fibbonacci Number (http://acm.hdu.edu.cn/showproblem.php?pid=2070),可以Accepted。
【例2】还是斐波那契数列
问题描述
大家都知道,斐波那契数列是满足如下性质的一个数列:
F(n)=1 (n≤2)
F(n)=F(n−1)+F(n−2) (n≥3)
请你求出 F(n) mod 109 +7 的值。
输入
一行一个正整数n(1≤n<263)
输出
输出一行一个整数表示答案。
输入样例
5
输出样例
5
(1)编程思路。
由于n值最大可到263,因此直接用递推的方式效率太低。构造一个矩阵,采用矩阵快速幂运算进行求解。
(2)源程序。
#include <stdio.h> #define MODNUM 1000000007 struct Matrix { long long s11 , s12 , s21 , s22 ; }; typedef struct Matrix matrix; matrix f(matrix a,matrix b) { matrix p ; p.s11 = (a.s11*b.s11 + a.s12*b.s21)%MODNUM; p.s12 = (a.s11*b.s12 + a.s12*b.s22)%MODNUM; p.s21 = (a.s21*b.s11 + a.s22*b.s21)%MODNUM; p.s22 = (a.s21*b.s12 + a.s22*b.s22)%MODNUM; return p ; } matrix quickpow(matrix p,long long n) // 采用递归的方法实现矩阵快速幂运算 { matrix q ; q.s11 = q.s22 = 1 ; // 初始化为单位矩阵 q.s12 = q.s21 = 0 ; if (n == 0) return q ; q = quickpow(p,n/2); q = f(q,q); if (n%2) q = f(q,p); return q ; } int main() { long long n ; matrix p ; scanf("%lld", &n); p.s11 = p.s12 = p.s21 = 1 ; p.s22 = 0 ; p = quickpow(p,n); printf("%lldn", p.s12); return 0; }
将上面的源程序提价给洛谷题库 P1962 斐波那契数列 (https://www.luogu.com.cn/problem/P1962),可以Accepted。
【例3】Fibonacci的数字
问题描述
经过一年的努力,数学神童zouyu终于把0到100000000的Fibonacci数列
(f[0]=0,f[1]=1;f[i] = f[i-1]+f[i-2](i>=2))的值全部给背了下来。
接下来,CodeStar决定要考考他,于是每问他一个数字,zouyu就要把答案说出来,不过有的数字太长了。所以规定超过8位的只要说出前4位和后4位就可以了。
输入
输入若干数字n(0 <= n <= 100000000),每个数字一行。读到文件尾。
输出
输出f[n]的前4个数字和后4个数字(若不超过8个数字,就全部输出)。
输入样例
0
1
35
39
40
65
输出样例
0
1
9227465
63245986
1023...4155
1716...7565
(1)编程思路。
通过输入输出样例可知,当n的值超过39时,斐波那契数列的位数不超过8位。因此先定义一个数组int f[41],通过递推的方法直接将n<40时f[n]的值求出并保存,若输入的n值小于40,直接输出求得的f[n]值。
当n值较大时,f[n]的位数超过了8位。求后4位比较容易,采用求10000的模即可,使用矩阵快速幂运算来完成,参见上面的例2。
前4位不同于后4位,要考虑进位问题,不能直接取模。将每个f[n]求出来,无论时间复杂度还是空间复杂度都要求太高。
我们知道,斐波那契数列第n项F[n]的通项公式是
将公式两端取对数可得
其中第三部分非常小,当n很大时趋近于0,可以忽略掉。
再由求得的log10(F[n])求出其高4位。具体方法以f[40]为例说明。
f[40]=102334155
log10(102334155)=log10(1.02334155*108)=log10(1.02334155)+8,
取其小数部分 log10(1.02334155)=0.0100206078,
10^0.0100206078=1.02334155,结果乘以1000取整,即为高4位1023。
(2)源程序。
#include <stdio.h> #include <math.h> typedef struct { int s11 , s12 , s21 , s22 ; }Matrix; Matrix f(Matrix a,Matrix b) { Matrix p ; p.s11 = (a.s11*b.s11 + a.s12*b.s21)%10000; p.s12 = (a.s11*b.s12 + a.s12*b.s22)%10000; p.s21 = (a.s21*b.s11 + a.s22*b.s21)%10000; p.s22 = (a.s21*b.s12 + a.s22*b.s22)%10000; return p ; } Matrix quickpow(Matrix p,int n) // 采用递归的方法实现矩阵快速幂运算 { Matrix q ; q.s11 = q.s22 = 1 ; // 初始化为单位矩阵 q.s12 = q.s21 = 0 ; if (n == 0) return q ; q = quickpow(p,n/2); q = f(q,q); if (n%2) q = f(q,p); return q ; } int main() { int f[51]={0,1,1},i; for (i=3;i<=40;i++) { f[i]=f[i-1]+f[i-2]; } int n; while (scanf("%d",&n)!=EOF) { if (n<=39) printf("%dn", f[n]); else { Matrix p ; p.s11 = p.s12 = p.s21 = 1 ; p.s22 = 0 ; p = quickpow(p,n); double s = log10(1.0 / sqrt(5.0)) + n * log10((1 + sqrt(5.0)) / 2); s -= (int)(s); s = pow(10, s); while (s < 1000) s *= 10; printf("%d...%04dn", (int)(s),p.s12); } } return 0; }
将上面的源程序提价给HDU题库HDU 3117 Fibonacci Numbers (http://acm.hdu.edu.cn/showproblem.php?pid=3117),可以Accepted。
HDU题库中的 HDU 1568 Fibonacci (http://acm.hdu.edu.cn/showproblem.php?pid=1568)只要求求出f[n]的高4位,将上面的程序进行简化如下,提交后同样可以Accepted。
#include <stdio.h> #include <math.h> int main() { int f[41]={0,1,1},i,len; for (i=3;i<=40;i++) { f[i]=f[i-1]+f[i-2]; } int n; while (scanf("%d",&n)!=EOF) { if (n<=20) printf("%dn", f[n]); else { double s = log10(1.0 / sqrt(5.0)) + n * log10((1 + sqrt(5.0)) / 2); s -= (int)(s); s = pow(10, s); while (s < 1000) s *= 10; printf("%dn", (int)(s)); } } return 0; }
【例4】有多少个斐波那契数
问题描述
输入两个整数a和b,求这两个数中间有多少个斐波那契数。
输入
输入包含几个测试用例。每个测试用例由两个非负整数a和b组成。输入以a=b=0终止。a<=b<=10100。数字a和b没有多余的前导零。
输出
对于每个测试用例,用单独一行输出Fibonacci数fi(a<=fi<=b)的数量。
输入样例
10 100
1234567890 9876543210
0 0
输出样例
5
4
(1)编程思路1。
先预先计算一下,斐波那契数列第480项的数字位数将达到100位,因此可以用字符串数组将斐波那契数列中前480项的数计算出来并保存下来。计算时采用高精度加法运算即可。显然这个字符串数组是按数字升序排列的。对于输入的字符串a和b,可以通过二分查找得到一个区间[left,right]使得第left个斐波那契数刚好大于a,第right+1个斐波那契数刚好大于b,这样left~right间斐波那契数的个数就是所求,注意第right个数刚好等于b时需要加1个。
(2)源程序1。
#include <stdio.h> #include <string.h> #define MAXLEN 110 #define LAST MAXLEN-2 char fib[481][MAXLEN]; // 存储480个斐波那契数 void bigNumAdd(char a[],char b[],char c[]) { int i,j,n1,n2,n; int num1[MAXLEN]={0},num2[MAXLEN]={0}; // 将a和b中存储的字符串形式的整数转换到num1和num2中去, // num1[0]对应于个位、num1[1]对应于十位、…… n1 = strlen(a); j = 0; for (i = n1 - 1;i >= 0 ; i --) num1[j++] = a[i] - '0'; n2 = strlen(b); j = 0; for (i = n2 - 1;i >= 0 ; i --) num2[j++] = b[i] - '0'; n=n1>n2?n1:n2; for (i = 0;i < n ; i ++ ) { num1[i] += num2[i]; // 逐位相加 if (num1[i] >= 10 ) // 处理进位 { num1[i] -= 10; num1[i+1] ++; } } j=0; if (num1[n]!=0) c[j++]=num1[n]+'0'; for(i=n-1; i>=0; i--) c[j++]=num1[i]+'0'; c[j]='