C++ 算法竞赛题解:P13569 [CCPC 2024 重庆站] osu!mania —— 浮点数精度陷阱与 `eps` 的深度解析
2026/5/22 9:21:03 网站建设 项目流程

文章目录

      • 🎵 一、题目背景
      • 🧮 二、核心公式与要求
        • 1. 准确率 (Acc)
        • 2. 个人表现 (PP)
      • ⚠️ 三、关键难点:浮点数精度与 `eps` 的深度分析
        • 1. 什么是精度误差?
        • 2. 为什么会导致错误?
        • 3. 解决方案:引入 `eps`
      • 💻 四、完整代码实现
      • 📊 五、样例验证
      • 📝 六、总结

摘要:本文详细解析了 CCPC 2024 重庆站的模拟题目“osu!mania”。我们将通过这道题深入探讨计算机浮点数运算的精度误差问题,并重点讲解如何使用eps(极小值)来修正四舍五入时的计算错误,确保算法的鲁棒性。

🎵 一、题目背景

本题目来自仓库 CCPC-CQ-2024。

osu! 是一款风靡全球的音乐游戏,其中的 osu!mania 模式是一款下落式节奏游戏。玩家在游玩过程中,对每个音符的打击会有不同的判定结果。我们需要根据给定的判定数量,计算出玩家的准确率 (Accuracy)个人表现 (PP)

🧮 二、核心公式与要求

给定判定结果的数量分别为a , b , c , d , e , f a, b, c, d, e, fa,b,c,d,e,f(分别对应 MAX, 300, 200, 100, 50, MISS),以及谱面 PP 上限ppmax \text{ppmax}ppmax

1. 准确率 (Acc)

计算公式:
Acc = 300 a + 300 b + 200 c + 100 d + 50 e + 0 f 300 × 总音符数 × 100 % \text{Acc} = \frac{300a + 300b + 200c + 100d + 50e + 0f}{300 \times \text{总音符数}} \times 100\%Acc=300×总音符数300a+300b+200c+100d+50e+0f×100%

  • 输出要求:四舍五入保留两位小数(即精确到10 − 4 10^{-4}104)。
2. 个人表现 (PP)

计算公式:
pp = max ⁡ ( 0 , 320 a + 300 b + 200 c + 100 d + 50 e + 0 f 320 × 总音符数 − 0.8 ) × 5 × ppmax \text{pp} = \max\left(0, \frac{320a + 300b + 200c + 100d + 50e + 0f}{320 \times \text{总音符数}} - 0.8\right) \times 5 \times \text{ppmax}pp=max(0,320×总音符数320a+300b+200c+100d+50e+0f0.8)×5×ppmax

  • 输出要求:四舍五入到最接近的整数。

⚠️ 三、关键难点:浮点数精度与eps的深度分析

这道题虽然逻辑简单,但极易因为浮点数精度误差而 WA(Wrong Answer)。这也是本题最值得学习的地方。

1. 什么是精度误差?

在计算机中,浮点数(如double)是用二进制存储的。很多十进制小数(如 0.1)无法被精确表示,这会导致计算结果存在微小的偏差。

例如,理论上计算结果应该是2687.5,但由于计算过程中的微小误差,计算机内部存储的可能是2687.4999999999

2. 为什么会导致错误?
  • 对于 Acc:如果理论值是96.20,但计算结果是96.199999,使用setprecision(2)输出时会变成96.19,导致答案错误。
  • 对于 PP:C++ 的round()llround()函数对于.5的处理是向远离零的方向取整。但如果2687.5变成了2687.499999round()会错误地将其变成2687而不是正确的2688
3. 解决方案:引入eps

我们定义一个极小的常量:

constdoubleeps=1e-8;// 0.00000001

在计算结果上加上这个极小值,可以抵消因二进制存储造成的微小负误差,确保四舍五入时能正确进位,但又不会影响整数部分的正确性。


💻 四、完整代码实现

#include<bits/stdc++.h>usingnamespacestd;intmain(){// 定义极小值 eps,用于修正浮点数计算中的精度误差// 例如,当理论结果是 100.00 时,计算机可能算出 99.999999// 加上一个极小的 eps (如 1e-8) 可以确保四舍五入时正确进位constdoubleeps=1e-8;intT;// 测试数据组数cin>>T;while(T--){longlongppmax;// 谱面pp上限cin>>ppmax;// 读取6种判定的数量: MAX, 300, 200, 100, 50, MISSlonglonga,b,c,d,e,f;cin>>a>>b>>c>>d>>e>>f;// 计算总音符数量longlongtotal=a+b+c+d+e+f;// --- 计算准确率 (Accuracy) ---// 公式: (300a + 300b + 200c + 100d + 50e + 0f) / (300 * total) * 100%doubleacc=(300.0*a+300.0*b+200.0*c+100.0*d+50.0*e+0.0*f)/(300.0*total)*100.0;// --- 计算个人表现 (PP) ---// 第一步:计算内部比率// 公式: (320a + 300b + 200c + 100d + 50e + 0f) / (320 * total) - 0.8doublepp_ratio=(320.0*a+300.0*b+200.0*c+100.0*d+50.0*e+0.0*f)/(320.0*total)-0.8;// 第二步:如果比率小于0,PP为0;否则继续计算// max(0, pp_ratio) 确保了不会出现负分doublepp_raw=max(0.0,pp_ratio)*5.0*ppmax;// --- 处理输出与精度修正 ---// 1. Acc 输出:需要保留两位小数// 加上 eps 是为了防止 96.20 变成 96.199999 导致输出 96.19// 2. PP 输出:需要四舍五入为整数// llround() 是将浮点数四舍五入为 long long 类型// 同样加上 eps 以防止 2687.5 变成 2687.499999 导致向下取整错误cout<<fixed<<setprecision(2)<<acc+eps<<"% ";// 设置小数点后2位cout<<llround(pp_raw+eps)<<endl;// 四舍五入为整数}return0;}

📊 五、样例验证

输入:

2 630 3029 2336 377 41 10 61 3000 20000 10000 0 0 0 0

输出:

96.20% 423 100.00% 2688

分析:

  1. 第一组数据严格按照题目公式计算,注意eps确保了96.20的正确显示。
  2. 第二组数据中,PP 的中间计算结果理论上是2687.5。如果没有eps,由于精度误差可能向下取整为2687。加上eps后,数值变为2687.50000001,成功向上取整为2688

📝 六、总结

这道题是典型的模拟题,考察点在于:

  1. 对数学公式的代码实现能力。
  2. 对计算机浮点数存储机制的理解。
  3. 鲁棒性编程:在涉及浮点数四舍五入时,养成加上eps的习惯,可以避免绝大多数因精度导致的离谱错误。

希望这篇题解对你理解浮点数精度处理有所帮助!

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询