2018级 1/2/中澳班 第二次上机测试
Problem B: Fibonacci Numbers
- Descpription
A Fibonacci sequence is calculated by adding the pervious two members of the sequence, with the first two members being both 1.
f ( 1 ) = 1 , f ( 2 ) = 1 , f ( n > 2 ) = f ( n − 1 ) + f ( n − 2 ) f(1)=1,f(2)=1,f(n>2)=f(n-1)+f(n-2) f(1)=1,f(2)=1,f(n>2)=f(n−1)+f(n−2)
Your task is to take a number as input, and print that fibonacci number.
- Input Format
A singal number indicates n n n.
- Output Format
A singal line indicates f ( n ) f(n) f(n).
- Simple Input
100 100 100
- Simple Output
354224848179261915075 354224848179261915075 354224848179261915075
- Hint
No generated fibonacci number in excess of 1000 digits will be in the test data, i.e. f ( 20 ) = 6765 f(20)=6765 f(20)=6765 has 4 digits.
- Analysis
题面很简单,就是求斐波那契数列的第n项,根据递推式,可以很轻松的写出来。
//递归版
int f(int x){
if (x<3) return 1;
else return f(x-1)+f(x-2);
}
//非递归版
f[1]=f[2]=1;
for (int i=3;i<=n;i++) f[i]=f[i-1]+f[i-2];
首先我们来比较一下这两个程序,应该都不难理解。(在我印象中,递归版老师是在课堂上讲过的,非递归版讲没讲我就不清楚了)相同点很显然,就是他们都能算出正确答案。不同点在于,二者时间复杂度不等。(所谓时间复杂度,直观理解就是一个算法的时间效率,以后应该会反复提及,在此不多赘述,有兴趣的同学请自行百度)
递归版时间复杂度分析:
求解f(n),必须先计算f(n-1)和f(n-2),计算f(n-1)和f(n-2),又必须先计算f(n-3)和f(n-4)……以此类推,直至必须先计算f(1)和f(0),然后逆推得到f(n-1)和f(n-2)的结果,从而得到f(n)要计算很多重复的值,在时间上造成了很大的浪费,算法的时间复杂度随着N的增大呈现指数增长,时间的复杂度为 O ( 2 n ) O(2^n) O(2n)。
非递归版时间复杂度分析:
从n(>2)开始计算,用f(n-1)和f(n-2)两个数相加求出结果,这样就避免了大量的重复计算,它的效率比递归算法快得多,算法的时间复杂度与n成正比,即算法的时间复杂度为 O ( n ) O(n) O(n)。
当然,以上并不是本题的重点,本题的难点在于所计算出的f(n)的值可以达到1000位。我们知道,int类型的范围在 − 2 31 -2^{31} −231 ~ 2 31 − 1 2^{31}-1 231−1,long long类型的范围在 − 2 63 -2^{63} −263 ~ 2 63 − 1 2^{63}-1 263−1,即使是double类型的范围也只有大概 1.7 ∗ 1 0 308 1.7*10^{308} 1.7∗10308。诚然long double类型可以表示到 1.2 ∗ 1 0 4932 1.2*10^{4932} 1.2∗104932,但考过计概的大家都知道,浮点数存在精度问题,在本题显然无法适用。
所以在此引入一种新的定义,高精度计算。
就我个人理解,所谓高精度,就是把一个一般情况下难以用基本数据类型表达的数(下文统称为大数),用数组或者字符串等线性结构来表达。例如我们用一个数组a来表示一个数A,一般情况下,用a[1]来表示A的个位,a[2]来表示A的十位……特别地,我喜欢用a[0]来表示A的位数(因为可以少开一个变量)。至于为什么要用a[1]来表示个位这么反人类的表示方法呢?读者可在此略作思考,下文将会提及。那么A=1234509876就用a[]={10,6,7,8,9,0,5,4,3,2,1}来表示。字符串也是相同的道理。
那么对于其计算问题,因其算是我们自定义的一种类型,就无法直接使用 + - * / 来计算,就需要我们自己对其定义。
我们先以加法为例,回想一下小学初学加法的时候,对于两个三位数四位数加法计算,我们当时是如何处理的?竖式计算:从末尾起,逐位逐位相加,有进位的要注意进位。是不是非常简单!值得注意的是,当我们算到最高位时,若还有进位,那么仍需进位,这也就是为什么采用倒序储存的原因。
//c=a+b
c[0]=max(a[0],b[0]);
for (int i=1;i<=c[0];i++){
c[i]+=a[i]+b[i];
c[i+1]+=c[i]/10;
c[i]%=10;
}
if (c[c[0]+1]==1) c[0]++;
输出也很简单,我们知道了这个数的数位,那么倒序输出即可。
//输出a
for (int i=a[0];i>=1;i--) cout<<a[i];
对于本题,以上两种操作就足够了,至于其他算术操作,原理和加法一样,都是模拟竖式计算,读者可先自行思考如何实现,若有需求,笔者可以再写一章关于高精度计算的文章,亦欢迎大家询问。
最后,附上本题标程。
#include <bits/stdc++.h>
using namespace std;
const int N=1007;
struct MyClass{
int BigNumber[N],Len;
void clear(){memset(BigNumber,0,sizeof BigNumber);}
}a,b,c;
MyClass operator + (MyClass &a,MyClass &b){
MyClass c; c.clear(); c.Len=max(a.Len,b.Len);
for (int i=0;i<c.Len;i++)
c.BigNumber[i+1]+=(c.BigNumber[i]+=a.BigNumber[i]+b.BigNumber[i])/10,c.BigNumber[i]%=10;
c.Len+=c.BigNumber[c.Len]!=0;
return c;
}
void Write(MyClass ans){ for (int i=ans.Len-1;i>=0;i--) printf("%d",ans.BigNumber[i]); printf("\n"); }
int main(){
int n;
cin>>n;
a.Len=a.BigNumber[0]=1; b=a;
for (int i=3;i<=n;i++){
c=a+b;
a=b;
b=c;
}
Write(c);
}
以上。若有不足,不吝赐教。
by Bobby