【SCUT_SE】 2018级 1/2/中澳班 第二次上机测试 Problem B

本文讨论了递归和非递归方法计算斐波那契数列的时间复杂度,指出递归方法的时间复杂度为O(2^n)而非递归方法为O(n)。此外,还提到了在处理大数时引入高精度计算的必要性,并简单介绍了高精度计算的基本思想和加法操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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(n1)+f(n2)

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 2311,long long类型的范围在 − 2 63 -2^{63} 263 ~ 2 63 − 1 2^{63}-1 2631,即使是double类型的范围也只有大概 1.7 ∗ 1 0 308 1.7*10^{308} 1.710308。诚然long double类型可以表示到 1.2 ∗ 1 0 4932 1.2*10^{4932} 1.2104932,但考过计概的大家都知道,浮点数存在精度问题,在本题显然无法适用。

所以在此引入一种新的定义,高精度计算

就我个人理解,所谓高精度,就是把一个一般情况下难以用基本数据类型表达的数(下文统称为大数),用数组或者字符串等线性结构来表达。例如我们用一个数组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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值