文章目录
-
-
- [🎵 一、题目背景](#🎵 一、题目背景)
- [🧮 二、核心公式与要求](#🧮 二、核心公式与要求)
-
- [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.499999,round()会错误地将其变成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
分析:
- 第一组数据严格按照题目公式计算,注意
eps确保了96.20的正确显示。 - 第二组数据中,PP 的中间计算结果理论上是
2687.5。如果没有eps,由于精度误差可能向下取整为2687。加上eps后,数值变为2687.50000001,成功向上取整为2688。
📝 六、总结
这道题是典型的模拟题,考察点在于:
- 对数学公式的代码实现能力。
- 对计算机浮点数存储机制的理解。
- 鲁棒性编程 :在涉及浮点数四舍五入时,养成加上
eps的习惯,可以避免绝大多数因精度导致的离谱错误。
希望这篇题解对你理解浮点数精度处理有所帮助!