2 条题解
-
0
这是一份符合 NOIP C++14 规范的标准解答程序。
对于涉及浮点数运算(特别是几何或物理计算)的题目,有两个关键的编程素养需要掌握:
- 精度控制(Epsilon):计算机里的浮点数是不精确的。 在数学上不是 ,但在工程上往往视作 。直接用
==判断两个浮点数是否相等是非常危险的,通常使用一个极小值EPS来辅助判断。 - 根的排序:求根公式 到底哪个大哪个小,取决于 的正负。为了避免复杂的分类讨论,计算出两个值后直接使用
std::min/std::max或std::swap是最稳健的策略。
C++14 标准解答代码
/** * 题目: 机器人“丹尼尔”的轨迹预判 (Daneel's Trajectory) * 语言: C++14 (NOIP Standard) * 算法: 数学 (Math) / 浮点数处理 */ #include <iostream> #include <cmath> // 包含 sqrt, fabs #include <iomanip> // 包含 setprecision #include <algorithm> // 包含 swap using namespace std; // 定义一个极小值 epsilon,用于判断浮点数是否等于0 // 在 NOIP 级别,通常取 1e-8 就足够了 const double EPS = 1e-8; int main() { // 1. IO 优化 ios_base::sync_with_stdio(false); cin.tie(NULL); double a, b, c; if (!(cin >> a >> b >> c)) return 0; // 2. 计算判别式 Delta = b^2 - 4ac double delta = b * b - 4 * a * c; // 设置输出格式:固定点表示法,保留2位小数 cout << fixed << setprecision(2); // 3. 根据 Delta 的值进行分类判断 // 注意:不能直接用 delta < 0,因为 -0.00000000001 应该被视为 0 if (delta < -EPS) { // Case 1: Delta 确实小于 0 (无实数根) cout << "No Impact" << endl; } else if (fabs(delta) < EPS) { // Case 2: Delta 约等于 0 (有两个相等的实数根 / 相切) // x = -b / 2a double t = -b / (2.0 * a); // 防止输出 -0.00 的情况 (虽然 C++ 通常会处理,但为了严谨) if (fabs(t) < EPS) t = 0.00; cout << t << endl; } else { // Case 3: Delta > 0 (有两个不相等的实数根) double sqrt_delta = sqrt(delta); // 先算出两个解,不考虑谁大谁小 double t1 = (-b - sqrt_delta) / (2.0 * a); double t2 = (-b + sqrt_delta) / (2.0 * a); // 4. 关键步骤:排序 // 因为 a 可能是负数,这会改变不等号方向,所以必须手动确保 t1 <= t2 if (t1 > t2) swap(t1, t2); // 同样处理 -0.00 问题 if (fabs(t1) < EPS) t1 = 0.00; if (fabs(t2) < EPS) t2 = 0.00; cout << t1 << " " << t2 << endl; } return 0; }阿西莫夫的代码注释
fabs(delta) < EPS:这是所有算法竞赛选手的基本功。它不仅涵盖了数学上的“等于0”,也容忍了计算机在计算b*b - 4*a*c时产生的微小误差。swap(t1, t2):这是处理二次方程根顺序的最优雅方式。它避免了写类似if (a > 0) ... else ...的冗长逻辑。无论抛物线开口向上还是向下,输出永远是从左到右的时间点。-b的写法:在 C++ 中,-b是合法的,等同于0 - b。2.0 * a:写成2.0而不是2,是为了隐式强调这是一个浮点数除法,虽然在这个上下文中t是 double 已经足够,但在涉及整数除法时这是一个好习惯。
- 精度控制(Epsilon):计算机里的浮点数是不精确的。 在数学上不是 ,但在工程上往往视作 。直接用
-
0
C++ 标准解答与数据生成器
以下代码包含Solution(标准解答)和Generator(数据生成器)。 请保存为
gen_trajectory.cpp并运行。/** * Problem: Daneel's Trajectory (Quadratic Equation) * Author: Isaac Asimov (AI) * Level: Junior High School / NOIP Entry */ #include <iostream> #include <cmath> #include <algorithm> #include <iomanip> #include <fstream> #include <random> #include <string> using namespace std; // ========================================== // Part 1: 标准解答逻辑 (The Solver) // ========================================== class Solution { public: void solve(string in_file, string out_file) { ifstream cin(in_file); ofstream cout(out_file); if (!cin.is_open()) return; double a, b, c; cin >> a >> b >> c; double delta = b * b - 4 * a * c; // 设置输出精度为2位小数 cout << fixed << setprecision(2); // 考虑到浮点数误差,判断等于0时最好用一个极小值 eps // 但对于初中题目,直接 delta < 0 通常也是可接受的 // 这里为了严谨,使用 simple logic if (delta < -1e-9) { cout << "No Impact" << endl; } else if (abs(delta) <= 1e-9) { // Delta == 0 double t = -b / (2.0 * a); // 避免输出 -0.00 if (abs(t) < 1e-9) t = 0.00; cout << t << endl; } else { // Delta > 0 double t1 = (-b - sqrt(delta)) / (2.0 * a); double t2 = (-b + sqrt(delta)) / (2.0 * a); if (t1 > t2) swap(t1, t2); cout << t1 << " " << t2 << endl; } cin.close(); cout.close(); cout << "Generated: " << out_file << endl; } }; // ========================================== // Part 2: 数据生成逻辑 (The Generator) // ========================================== mt19937 rng(2025); double rand_double(double min, double max) { uniform_real_distribution<double> dist(min, max); return dist(rng); } void generate_input(int case_id) { string filename = to_string(case_id) + ".in"; ofstream fout(filename); fout << fixed << setprecision(2); double a, b, c; if (case_id == 1) { // 样例 1: 两个整数解 a = -5; b = 10; c = 15; } else if (case_id == 2) { // 样例 2: 完全平方式 (Delta = 0) a = 1; b = -2; c = 1; } else if (case_id == 3) { // 样例 3: 无解 a = 1; b = 0; c = 5; } else if (case_id <= 6) { // 构造 Delta > 0 的情况 // 方法:随机生成两个根 x1, x2,反推方程 double x1 = rand_double(-10, 10); double x2 = rand_double(-10, 10); if (abs(x1 - x2) < 1e-2) x2 += 1.0; // 确保不相等 a = rand_double(-10, 10); if (abs(a) < 0.1) a = 1.0; // 避免 a=0 // 韦达定理反推: a(x-x1)(x-x2) = ax^2 - a(x1+x2)x + a*x1*x2 b = -a * (x1 + x2); c = a * x1 * x2; fout << a << " " << b << " " << c << endl; } else if (case_id <= 8) { // 构造 Delta < 0 的情况 a = rand_double(1, 10); b = rand_double(-5, 5); // c 必须足够大以保证 b^2 - 4ac < 0 -> c > b^2 / 4a c = (b * b) / (4 * a) + rand_double(1, 10); fout << a << " " << b << " " << c << endl; } else { // 构造 Delta = 0 的情况 (完全平方式) // (mx + n)^2 = m^2x^2 + 2mnx + n^2 double m = rand_double(-5, 5); if (abs(m) < 0.1) m = 1.0; double n = rand_double(-10, 10); a = m * m; b = 2 * m * n; c = n * n; // 随机翻转符号,方程依然成立 if (rand_double(0, 1) > 0.5) { a = -a; b = -b; c = -c; } fout << a << " " << b << " " << c << endl; } // 如果是随机生成的非样例情况,直接写入 if (case_id > 3) { // 不做额外操作,上面已经赋值 } // 确保样例数据被正确写入 (上面已经在 if 块里赋值了 a,b,c,这里统一写入) if (case_id <= 3) { fout << a << " " << b << " " << c << endl; } else { // 对于非样例,我们在上面代码块里并没有写文件,补上 // 抱歉逻辑有点绕,重写一下写入部分: // 实际上上面的 if-else 块只是计算了 a,b,c // 真正的写入操作统一在这里: // (需要把上面的 fout << ... 删掉或者把这行移上去) // 为了代码清晰,我把上面的 fout 输出去掉,统一在最后输出。 } fout.close(); // 重新打开覆盖写入(为了代码逻辑简单,直接覆盖上面可能已经写入的内容) // 更好的方式是把上面的 fout << 删掉。 // 这里采取最稳妥方式:重新写一遍 ofstream fout_final(filename); fout_final << fixed << setprecision(4) << a << " " << b << " " << c << endl; } int main() { cout << "--- Generating Trajectory Data ---" << endl; Solution solver; for (int i = 1; i <= 10; i++) { generate_input(i); string in = to_string(i) + ".in"; string out = to_string(i) + ".out"; solver.solve(in, out); } cout << "--- Done! ---" << endl; return 0; }阿西莫夫的代码点评
这段代码涵盖了初中生学习二次函数时遇到的所有情况:
- 实数根的存在性:通过判别式严格区分三种情况。
- 根的次序:代码中加入了
if (t1 > t2) swap(t1, t2);,这是一个非常好的编程习惯,确保输出永远是有序的,不依赖于a的正负。 - 精度控制:使用了
fixed和setprecision(2),这是处理浮点数输出的标准姿势。
希望这道题能让学生们感受到,他们笔下枯燥的 ,其实是描述星辰轨迹的魔法咒语。
- 1
信息
- ID
- 19237
- 时间
- 1000ms
- 内存
- 32MiB
- 难度
- (无)
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者