C++ 算法竞赛题解:P13569 [CCPC 2024 重庆站] osu!mania —— 浮点数精度陷阱与 `eps` 的深度解析

文章目录

      • [🎵 一、题目背景](#🎵 一、题目背景)
      • [🧮 二、核心公式与要求](#🧮 二、核心公式与要求)
        • [1. 准确率 (Acc)](#1. 准确率 (Acc))
        • [2. 个人表现 (PP)](#2. 个人表现 (PP))
      • [⚠️ 三、关键难点:浮点数精度与 `eps` 的深度分析](#⚠️ 三、关键难点:浮点数精度与 eps 的深度分析)
        • [1. 什么是精度误差?](#1. 什么是精度误差?)
        • [2. 为什么会导致错误?](#2. 为什么会导致错误?)
        • [3. 解决方案:引入 `eps`](#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, f a,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} 10−4)。
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+0f−0.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

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

cpp 复制代码
const double eps = 1e-8; // 0.00000001

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


💻 四、完整代码实现

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

int main() {
    // 定义极小值 eps,用于修正浮点数计算中的精度误差
    // 例如,当理论结果是 100.00 时,计算机可能算出 99.999999
    // 加上一个极小的 eps (如 1e-8) 可以确保四舍五入时正确进位
    const double eps = 1e-8; 

    int T; // 测试数据组数
    cin >> T;

    while (T--) {
        long long ppmax; // 谱面pp上限
        cin >> ppmax;

        // 读取6种判定的数量: MAX, 300, 200, 100, 50, MISS
        long long a, b, c, d, e, f;
        cin >> a >> b >> c >> d >> e >> f;

        // 计算总音符数量
        long long total = a + b + c + d + e + f;

        // --- 计算准确率 (Accuracy) ---
        // 公式: (300a + 300b + 200c + 100d + 50e + 0f) / (300 * total) * 100%
        double acc = (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.8
        double pp_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) 确保了不会出现负分
        double pp_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; // 四舍五入为整数
    }

    return 0;
}

📊 五、样例验证

输入:

text 复制代码
2
630
3029 2336 377 41 10 61
3000
20000 10000 0 0 0 0

输出:

text 复制代码
96.20% 423
100.00% 2688

分析:

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

📝 六、总结

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

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

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

相关推荐
耿雨飞1 小时前
Python 后端开发技术博客专栏 | 第 06 篇 描述符与属性管理 -- 理解 Python 属性访问的底层机制
开发语言·python
耿雨飞2 小时前
Python 后端开发技术博客专栏 | 第 08 篇 上下文管理器与类型系统 -- 资源管理与代码健壮性
开发语言·python
(Charon)2 小时前
【C++/Qt】C++/Qt 实现 TCP Server:支持启动监听、消息收发、日志保存
c++·qt·tcp/ip
2601_949194262 小时前
Python爬虫完整代码拿走不谢
开发语言·爬虫·python
jr-create(•̀⌄•́)2 小时前
正则化和优化算法区别
pytorch·深度学习·神经网络·算法
c***89202 小时前
python爬虫——爬取全年天气数据并做可视化分析
开发语言·爬虫·python
aq55356002 小时前
C语言、C++和C#:三大编程语言核心差异详解
java·开发语言·jvm
并不喜欢吃鱼3 小时前
从零开始C++----七.继承及相关模型和底层(上篇)
开发语言·c++
沐知全栈开发3 小时前
XML CDATA
开发语言