2 条题解

  • 0
    @ 2025-12-1 16:51:33

    这是一份符合 NOIP C++14 规范的标准解答程序。

    对于涉及浮点数运算(特别是几何或物理计算)的题目,有两个关键的编程素养需要掌握:

    1. 精度控制(Epsilon):计算机里的浮点数是不精确的。0.000000000010.00000000001 在数学上不是 00,但在工程上往往视作 00。直接用 == 判断两个浮点数是否相等是非常危险的,通常使用一个极小值 EPS 来辅助判断。
    2. 根的排序:求根公式 b±Δ2a\frac{-b \pm \sqrt{\Delta}}{2a} 到底哪个大哪个小,取决于 aa 的正负。为了避免复杂的分类讨论,计算出两个值后直接使用 std::min / std::maxstd::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;
    }
    

    阿西莫夫的代码注释

    1. fabs(delta) < EPS:这是所有算法竞赛选手的基本功。它不仅涵盖了数学上的“等于0”,也容忍了计算机在计算 b*b - 4*a*c 时产生的微小误差。
    2. swap(t1, t2):这是处理二次方程根顺序的最优雅方式。它避免了写类似 if (a > 0) ... else ... 的冗长逻辑。无论抛物线开口向上还是向下,输出永远是从左到右的时间点。
    3. -b 的写法:在 C++ 中,-b 是合法的,等同于 0 - b
    4. 2.0 * a:写成 2.0 而不是 2,是为了隐式强调这是一个浮点数除法,虽然在这个上下文中 t 是 double 已经足够,但在涉及整数除法时这是一个好习惯。
    • 0
      @ 2025-12-1 16:49:22

      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;
      }
      

      阿西莫夫的代码点评

      这段代码涵盖了初中生学习二次函数时遇到的所有情况:

      1. 实数根的存在性:通过判别式严格区分三种情况。
      2. 根的次序:代码中加入了 if (t1 > t2) swap(t1, t2);,这是一个非常好的编程习惯,确保输出永远是有序的,不依赖于 a 的正负。
      3. 精度控制:使用了 fixedsetprecision(2),这是处理浮点数输出的标准姿势。

      希望这道题能让学生们感受到,他们笔下枯燥的 ax2+bx+cax^2+bx+c,其实是描述星辰轨迹的魔法咒语。

      • 1

      信息

      ID
      19237
      时间
      1000ms
      内存
      32MiB
      难度
      (无)
      标签
      递交数
      0
      已通过
      0
      上传者