「浙江理工大学ACM入队200题系列」问题 L: 零基础学C/C++52——计算数列和2/1,3/2,5/3,8/5......

2022-12-11,,,,

本题是浙江理工大学ACM入队200题第五套中的L题

我们先来看一下这题的题面.

题面

题目描述

有一分数序列:2/1,3/2,5/3,8/5,13/8,21/13,…… 计算这个数列的前n项和。注意:C语言中整数/整数的结果为整数;需要用(float)强制转换为实型或乘以1.0后进行计算。

输入

输入一个正整数n。

输出

输出数列的前n项和(保留两位小数),输出格式可为:printf("s=%.2f\n",..);。

样例输入

10

样例输出

s=16.48

提示

C语言中整数/整数的结果为整数;注意用(float)强制转换为实型或乘以1.0后进行计算。


题目分析

都做到这题了,相信对于求和以及整数除整数什么的应该很熟悉了吧?这里就不多赘述了.

观察题目给出的数列,我们发现除第一项以外的每一项的分母是前一项的分子,而分子是前一项的分子和分母之和.这便是这组数列的递推规律,我们可以依照这个递推规律去不断算出每一项.

首先,我们需要定义初始状态,也就是第一项,因为第一项无法使用上面的递推规律.我们定义两个变量a和b,相信大家首选的都是int型,用来分别存放分子和分母,并给予他们第一项的值作为初始值,局部代码如下:

	int a = 2, b = 1; // 初始状态的定义,即第一项

由于是求第n项,我们使用计次循环(for循环)来完成,在循环体中,我们先计算出当前项然后加到累加器中,随后依照前面发现的规律推出下一个状态(即进行状态转移),说人话就是求下一项的分子和分母,局部代码如下:

	for(int i = 0;i < n;i++) // 共循环n次,请习惯i从0开始
{
sum += (double)a / b; // 累加器累加当前项(记得转为小数)
int t = b; // 这里和两个变量交换一样,必须要用临时变量保存其中一个值,不然在更新之后原本的值就消失了
b = a; // 更新分母,下一项的分母即为前一项的分子
a += t; // 更新分子,下一项的分子为前一项的分子(即这个变量本身存的值)和分母(此时分母已被更新,使用临时变量中保存的值)的和
}

如此不断循环理论上就可以推出这个数组的所有项了.这个思路形成很自然,但是其实我们已经在不知不觉中使用了一种非常高级的算法——动态规划(DP).这道题本质上是在动态求斐波那契数列某一项,而这便是DP的典型应用场景.第十八套的问题H也需要使用这种算法,不过和这题一样,即便你不知道什么是DP,你依旧可以很自然地推出能顺利解决那道题的正确思路(因为仁慈的叶教挑的都是简单题,基本不需要你懂很多算法).

DP是一种对新人朋友们来说非常难的算法,这边我只做提及,让大家体会到思维的强大和思想的乐趣,请各位新人朋友们不要继续深入了解,除非你对自己很有信心.就像前面说的那样,即便你不知道什么是DP,即时你不知道你在用DP,你依旧可以很轻松地推出这道题的正确思路.

常见错误思路及原因解析

当前,上述代码仅仅是正确思路而已,如果你就这样直接提交上去,迎接你的是答案错误.

今天又是做梦都是WA的一天呢

这里比较容易出现的一个错误是累加器sum没有初始化,不过你都做到这了,sum还不养成初始化的习惯是不是有点过分了,前面都做了这么多道累加的题了.

除此之外,就是传说中"三年OI一场空,不开long long见祖宗"所描述的典型错误.请大家反复默念并最好背诵这句话.

相信各位朋友应该已经知道了,变量的数据类型本质上是在内存中存储方式,而内存不可能是无限大的,因此在C中每个数据类型都有其范围(Python算是一个特例,它底层实现的高精度等算法允许它的数值型变量存储无限大的数据,只要内存还放得下).在32位系统下,int类型的变量仅能存放4个字节(32个二进制位,其中一个是符号位,所以有效的只有31位)的数据.一旦存放的数据超过4个字节,那么溢出那部分数字就会被直接丢弃.比如你在int里存了33位的数据,那么第33位(最高位)就会直接被无视,你存的是后32位的数据(由于符号位的问题,这个数据经常会很离谱).

此题便是出现了这个问题.大部分题目会给出数据范围,通过数据范围再加上一点点的经验我们可以相对比较容易地发现数据爆int的问题,但对于先前几乎没有遇到过爆int的情况的各位新人朋友们,再加上这道题没有给出数据范围,想独自发现这个错误确实有些困难.我们可以这么思考,我们解决此题的思路没有任何问题,所以只可能是在细节上出了问题,而数据类型是否合适便是其中一个考虑项.养成这样的找错习惯,便可以在一定的思考之后发现这里爆int的问题.

解决方案

解决这个问题的方法非常简单,只要把a和b的数据类型换成一个更大的就好了,这里可选的有long longdouble两个类型,一般我们改用long long(学过别的语言的朋友注意了,不是long,在C中大部分情况下longint是一样大的).不过在这道题里我们可以选择double,这样不仅能解决爆int的问题,还可以解决整数除整数的问题,一石二鸟.不过正常情况下还是建议开成long long,因为浮点数自身还有精度等其他的问题.

参考代码

下面给出了我自己做这道题时候的完整代码:

(仅作为参考,一定要自己写一下奥,作弊没意思,害人又害己)

#include <stdio.h>

int main()
{
int n;
scanf("%d", &n); double sum = 0; // 累加器,别忘了初始化
double a = 2, b = 1; // 初始状态的定义,即第一项
for (int i = 0; i < n; i++) // 共循环n次,请习惯i从0开始
{
sum += a / b;
double t = b; // 这里和两个变量交换一样,必须要用临时变量保存其中一个值,不然在更新之后原本的值就消失了
b = a; // 更新分母,下一项的分母即为前一项的分子
a += t; // 更新分子,下一项的分子为前一项的分子(即这个变量本身存的值)和分母(此时分母已被更新,使用临时变量中保存的值)的和
} printf("s=%.2f\n", sum);
return 0;
}

"正是我们每天反复做的事情,最终造就了我们,优秀不是一种行为,而是一种习惯" ---亚里士多德

这篇题解就到这里了,各位朋友如果有问题欢迎到acm成员群中提问哦!

「浙江理工大学ACM入队200题系列」问题 L: 零基础学C/C++52——计算数列和2/1,3/2,5/3,8/5......的相关教程结束。

《「浙江理工大学ACM入队200题系列」问题 L: 零基础学C/C++52——计算数列和2/1,3/2,5/3,8/5.......doc》

下载本文的Word格式文档,以方便收藏与打印。