浙大PAT 甲级A1104 Sum of Number Segments & 乙级B1049 数列的片段和(数学找规律+浮点数精度有限导致计算结果出错问题)

14 篇文章 0 订阅

PAT - A1104 Sum of Number Segments (20 分) https://pintia.cn/problem-sets/994805342720868352/problems/994805363914686464
PAT - B1049 数列的片段和 (20 分) https://pintia.cn/problem-sets/994805260223102976/problems/994805275792359424
题目-英文
题目-中文

大致思路:

目标求所有片段的和,若按题目描述的直接思路做——先算各片段的和再相加,实际上有很多重复计算(每个元素加了多次),大概率会超时(没实测),而且很不优雅。实际上每个元素加的次数是有规律的,如样例数据:

{ 0.1, 0.2, 0.3, 0.4 }
(0.1) (0.1, 0.2) (0.1, 0.2, 0.3) (0.1, 0.2, 0.3, 0.4) (0.2) (0.2, 0.3) (0.2, 0.3, 0.4) (0.3) (0.3, 0.4) (0.4)
n=4
0.1: i=0, 次数4=4
0.2: i=1, 次数6=3+3
0.3: i=2, 次数6=2+2+2
0.4: i=3, 次数4=1+1+1+1

归纳一下,每个元素总个数=(n-i)*(i+1),再与对应元素值乘起来再累加求和即为所求。

也可按柳神的思路,更“数学”、靠谱( i 从1开始,与上面不同):
将数列中的每个数字读取到 temp 中,假设我们选取的片段中包括 temp,且这个片段的首尾指针分别为 p 和 q,那么对于 p,有 i 种选择,即 1,2,…i,对于 q,有 n-i+1 种选择,即 i, i+1, … n,所以 p 和 q 组合形成的首尾片段有 i * (n-i+1) 种,因为每个里面都会出现 temp,所以 temp引起的总和为 temp * i * (n – i + 1)。

关键点:
  • 问题:仅测试点2(第三个)答案错误。
  • 原因:本题数列元素为小数,采用double类型(float 更弱)存储时,浮点型数据在计算机内部二进制存储(IEEE754 标准),有限的字长限制(截断)了尾数,即精度有限,大部分十进制小数(如0.1、0.2、0.3……)都无法精确表示,存储值与真实值(想要的十进制数)有微小误差,而这很小的误差在多次累加/做乘法时,积少成多便会显露出来,造成结果错误。
  • “解决”方法:将输入数据扩大1000倍(只是基于当前测试点数据不小于0.001,题目设计缺陷),即小数点后移,再转为整型精确存储,算完最后再恢复回小数。
  • 关于“解决”方法的原理浅析:输入的小数(不能精确表示的那些)在存储时由于前述误差的原因,截断舍去了尾数的一部分,相比真实值是略小一点的。
    在与其他数做乘法过程中,两数的尾数部分相乘超过尾数的位数,做舍入处理,舍入有多种处理方式,本人实测是用的“0舍1入法”,即若丢失的最高位(二进制)值为1,则给尾数末位加1修正,否则直接舍去。这样得到的存储、输出的乘积理论上仍是有误差的,可能略大一点、也可能略小一点。
    但个人实测多个数据发现巧合的是(不知道是什么数学原理),如果与能与之相乘获得整数的10的幂(即10、100、1000……且10的幂次要大于等于十进制数小数点后的位数,如0.3最少要乘10、0.03最少乘100……其他非10的幂的数也有个别的有同样效果,但大多不行)相乘时, 尾数恰好都是“1入”,而且进行“1入”之前的后半部分二进制值全1,也就是“1入”之后会持续向前进位,最终留下的尾数恰好等于应得的值,误差被神奇地“纠正”掉、“消失”了……最终效果即是实现了想要的右移十进制数的小数点,把小数部分的有效数字转到了整数部分。
    然后强制类型转换为整型(效果为截断、去掉小数点后的全0),便可在扩大10的幂倍的基础上 进行加法、乘法等,由于整数的二进制表示精确无误差,计算结果也便精确无误差了。
  • 部分参考资料:
    https://www.liuchuo.net/archives/448
    https://blog.zhengrh.com/post/about-double/
通过代码(C/C++):
#include<stdio.h>
int main(){
    int N;
    scanf("%d", &N);
    double temp;
    long long sum=0;
    for(int i=0;i<N;i++){
        scanf("%lf", &temp);
        // 找规律,每个元素总个数=(N-i)*(i+1)
        // 先乘1000(基于本题测试点情况,理论上1000不一定够)并转为整型,避免计算机内部二进制存储浮点型的误差累积影响最后结果
        // temp需在前,否则当N较大且i处于N/2时,后两者先乘积会超出其类型int表示范围而出错
        sum += (long long)(temp * 1000)*(N-i) * (i+1);
    }
    printf("%.2f\n", sum/1000.0);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值