2 条题解

  • 0
    @ 2025-12-9 16:25:01

    这是一个功能完备的数据生成器。它集成了标准题解逻辑(用于生成 .out)和针对性数据构造逻辑(用于生成 .in)。

    数据生成器功能说明

    1. 全自动生成:运行一次代码,即可在当前目录下生成 1.in/1.out10.in/10.out 共 20 个文件。
    2. 测试点覆盖策略
      • 测试点 1L=10L=10。极小值边界,预期输出为 0(最小勾股周长为 3+4+5=123+4+5=12),测试程序是否能正确处理“无解”情况。
      • 测试点 2L=12L=12。恰好等于最小勾股周长,测试边界条件。
      • 测试点 3L=30L=30。题目样例数据,确保样例通过。
      • 测试点 4-6L=100,250,500L=100, 250, 500。中小规模数据,测试基础逻辑。
      • 测试点 7-8L=1000,1500L=1000, 1500。大规模数据,测试程序在正常范围内的表现。
      • 测试点 9L=1999L=1999。接近最大值边界,奇数测试。
      • 测试点 10L=2000L=2000。题目允许的最大值,测试程序的效率上限。

    C++ 数据生成器代码

    /**
     * GESP 4级 [寻找勾股数] - 数据生成器
     * 功能:自动生成 1.in/1.out ~ 10.in/10.out
     * 包含:标准解法逻辑 + 精心挑选的测试点 L 值
     */
    
    #include <iostream>
    #include <fstream>  // 用于文件操作
    #include <string>   // 用于文件名生成
    #include <cmath>    // 用于 sqrt
    #include <vector>
    
    using namespace std;
    
    // ==========================================
    // Part 1: 标准题解逻辑 (用于生成 .out 文件)
    // ==========================================
    int solve(int L) {
        int ans = 0;
    
        // 枚举 a,上界约为 L/3
        for (int a = 3; a * 3 < L; ++a) {
            
            // 枚举 b,从 a+1 开始
            for (int b = a + 1; ; ++b) {
                long long c2 = 1LL * a * a + 1LL * b * b;
                int c = sqrt(c2);
    
                // 关键剪枝:如果当前周长已经超过 L,更大的 b 肯定也不行,退出内层循环
                if (a + b + c > L) {
                    break;
                }
    
                // 判定是否构成勾股数
                if (1LL * c * c == c2) {
                    ans++;
                }
            }
        }
        return ans;
    }
    
    // ==========================================
    // Part 2: 数据构造与文件生成
    // ==========================================
    void generate_data() {
        // 预设 10 个测试点的 L 值,确保覆盖各类情况
        int test_L[11] = {
            0,      // 占位符
            10,     // Case 1: 小于最小周长(12),结果应为 0
            12,     // Case 2: 恰好等于最小周长,结果应为 1 (3,4,5)
            30,     // Case 3: 题目样例
            100,    // Case 4: 小规模
            250,    // Case 5: 中小规模
            500,    // Case 6: 中等规模
            1000,   // Case 7: 大规模
            1500,   // Case 8: 较大规模
            1999,   // Case 9: 接近最大值
            2000    // Case 10: 最大值边界
        };
    
        cout << "Start generating data..." << endl;
    
        for (int i = 1; i <= 10; ++i) {
            // 构造文件名
            string in_name = to_string(i) + ".in";
            string out_name = to_string(i) + ".out";
    
            // 打开文件流
            ofstream fin(in_name);
            ofstream fout(out_name);
    
            if (!fin.is_open() || !fout.is_open()) {
                cerr << "Error opening file for case " << i << endl;
                continue;
            }
    
            // 1. 写入输入文件 (.in)
            int L = test_L[i];
            fin << L << endl;
    
            // 2. 运行标准算法并写入输出文件 (.out)
            int ans = solve(L);
            fout << ans << endl;
    
            // 关闭文件
            fin.close();
            fout.close();
    
            cout << "Generated Case " << i << ": L = " << L << ", Ans = " << ans << endl;
        }
    
        cout << "All done! 20 files generated." << endl;
    }
    
    int main() {
        // 优化 C++ 标准流速度(虽然在此非必要,但在竞赛中是好习惯)
        ios::sync_with_stdio(false);
        cin.tie(NULL);
    
        generate_data();
    
        return 0;
    }
    

    如何使用

    1. 将上述代码保存为 generator.cpp
    2. 编译并运行:
      g++ generator.cpp -o generator
      ./generator
      
    3. 运行结束后,你的文件夹下会出现 1.in10.in 以及对应的 1.out10.out 文件。
    4. 你可以将这些文件打包上传到 OJ(Online Judge)系统作为测试数据。
    • 0
      @ 2025-12-9 16:22:10

      你好!作为OI金牌教练,我为你整理了这道 GESP 4级难度题目《寻找勾股数》的标准题解代码。

      这份代码完全符合 NOIP C++14 竞赛标准,包含了详细的注释、复杂度分析以及针对更高阶选手的优化思考。

      标准题解代码 (C++14)

      /**
       * 题目:寻找勾股数 (Pythagorean Triples)
       * 难度:GESP 4级 / CSP-J 普及-
       * 算法:枚举 (Brute Force) + 剪枝优化
       */
      
      #include <iostream>
      #include <cmath>    // 用于 sqrt 函数
      #include <algorithm>
      
      using namespace std;
      
      // 建议开启 IO 优化,养成好习惯
      void fast_io() {
          ios::sync_with_stdio(false);
          cin.tie(NULL);
      }
      
      int main() {
          fast_io();
      
          int L;
          if (!(cin >> L)) return 0;
      
          int ans = 0;
      
          // 第一层循环:枚举边长 a
          // 优化1:确定 a 的上界
          // 因为 a < b < c 且 a + b + c <= L
          // 所以 3a < a + b + c <= L  =>  3a < L  =>  a < L/3
          // 即使写 a <= L 也没错,但效率会低一点
          for (int a = 3; a * 3 < L; ++a) {
              
              // 第二层循环:枚举边长 b
              // b 必须大于 a
              for (int b = a + 1; ; ++b) {
                  
                  // 易错点1:数据范围估算
                  // L <= 2000,平方和最大约为 2000*2000 = 4*10^6,远小于 int 上限 (2*10^9)
                  // 所以这里用 int 足够,不需要 long long
                  long long c2 = 1LL * a * a + 1LL * b * b; // 为了保险习惯性转 long long
                  
                  // 计算 c 的整数部分
                  int c = sqrt(c2);
      
                  // 关键点2:剪枝 (Pruning)
                  // 如果当前的 a, b 加上最小可能的 c (即 c > b) 已经超过 L,
                  // 那么更大的 b 肯定也不符合条件,直接退出内层循环。
                  // 此时 c 还没验证是否是整数,但我们可以确定 c >= b+1
                  if (a + b + c > L) {
                      break;
                  }
      
                  // 关键点3:判定完全平方数
                  // 浮点数比较有误差,最稳妥的方法是算出来整数 c 后,再平方回去看是否相等
                  if (1LL * c * c == c2) {
                      // 此时满足 a^2 + b^2 = c^2
                      // 且因为是从小到大枚举,自然满足 a < b < c
                      // 且前面剪枝保证了 a + b + c <= L
                      ans++;
                  }
              }
          }
      
          cout << ans << endl;
      
          return 0;
      }
      

      详细分析与思考

      1. 时间复杂度分析

      • 思考过程
        • 如果我们使用三层循环枚举 a,b,ca, b, c,复杂度是 O(L3)O(L^3)。当 L=2000L=2000 时,20003=8×1092000^3 = 8 \times 10^9,远超 C++ 每秒约 10810^8 次的运算极限,会超时 (TLE)
        • 我们根据 a,ba, b 计算出 cc,将三层循环降为两层循环。
        • 外层循环 aa 的范围大约是 L/3L/3
        • 内层循环 bb 的范围受限于 a+b+a2+b2La+b+\sqrt{a^2+b^2} \le L。粗略估计 bb 大约只能枚举到 L/2L/2 左右。
      • 结论:总的时间复杂度约为 O(L2)O(L^2)
        • L=2000L=2000 时,计算量大约在 10610^6 级别(几百万次),计算机可以在 10ms 内轻松完成。这完全符合 GESP 4级的时间要求。

      2. 空间复杂度分析

      • 分析:我们只使用了几个整型变量 (L, a, b, c, ans 等) 来存储数据,没有开辟数组。
      • 结论:空间复杂度为 O(1)O(1)

      3. 关键点与易错点

      1. 浮点数判等
        • 错误写法if (sqrt(a*a+b*b) == (int)sqrt(a*a+b*b))。虽然在 LL 较小时通常没问题,但这不是严谨的写法。
        • 正确写法:先取整 c = sqrt(sum),再反算 c*c == sum。这利用了整数运算的精确性。
      2. 循环退出条件 (剪枝)
        • 内层循环 b 没有写具体的终止条件(如 b < L),而是利用 if (a + b + c > L) break;。这是极其重要的一步。如果没有这个 break,程序虽然逻辑正确,但会变成 O(L2)O(L^2) 的“慢版”,做很多无用功;如果数据范围扩大到 10410^4,有剪枝的代码可能还能跑,没剪枝的就会慢很多。
      3. 变量溢出
        • 虽然本题 L=2000L=2000 不会溢出 int,但在处理类似题目(如 L=105L=10^5)时,a*a 可能会爆 int竞赛好习惯是在计算平方和时强制转换为 long long,如 1LL * a * a

      4. 更优解法(拓展思考 - 适合 GESP 6级+)

      如果数据范围加强到 L106L \le 10^6O(L2)O(L^2) 也会超时。这时需要用到数论知识:欧几里得公式生成勾股数。

      • 公式a=m2n2,b=2mn,c=m2+n2a = m^2 - n^2, \quad b = 2mn, \quad c = m^2 + n^2
        • 其中 m>n>0m > n > 0,且 gcd(m,n)=1\gcd(m, n) = 1,且 m,nm, n 一奇一偶(生成本原勾股数)。
        • 所有勾股数都可以由本原勾股数乘以倍数 kk 得到。
      • 复杂度:通过枚举 m,nm, n,复杂度可以降低到 O(LlogL)O(L \log L) 甚至接近 O(L)O(L)
      • 注:对于 GESP 4级考生,掌握 O(L2)O(L^2) 解法已经满分。
      • 1

      信息

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