3 条题解

  • 0
    @ 2025-11-28 11:44:44

    这是一个功能完整的 数据生成器。它包含了这道题的标准 AC 逻辑,并针对题目要求的各个数据范围(包括 b=1b=1bb 较小、a=1a=1 的陷阱、以及数值溢出边界)精心设计了 10 组测试数据。

    数据生成器代码 (gen.cpp)

    将以下代码保存为 gen.cpp,编译运行即可在当前目录下生成 1.in/out10.in/out

    #include <iostream>
    #include <fstream>
    #include <string>
    #include <vector>
    #include <cmath>
    #include <cstdlib>
    #include <ctime>
    
    using namespace std;
    
    // ==========================================
    // 第一部分:标准解答逻辑 (Solver)
    // 用于计算 .out 文件的正确答案
    // ==========================================
    long long solve(long long a, long long b) {
        // 1. 特判 a=1,防止 TLE
        if (a == 1) return 1;
    
        // 2. 暴力循环 + 剪枝
        long long ans = 1;
        // 题目要求的上限
        const long long LIMIT = 1000000000; 
    
        for (int i = 1; i <= b; i++) {
            // 检查溢出风险:
            // 如果 ans 已经大于 LIMIT,或者 ans * a 会超过 long long 范围(虽然题目限制下很难爆 ll)
            // 实际上题目 a, b <= 10^9,只要 ans > LIMIT 就 break 即可,
            // 不需要担心 long long 溢出,因为 ans 最多略大于 10^9 * 10^9 = 10^18 < 9e18
            
            ans *= a;
            
            if (ans > LIMIT) {
                return -1;
            }
        }
        return ans;
    }
    
    // ==========================================
    // 第二部分:数据生成逻辑
    // ==========================================
    int main() {
        srand(time(0)); // 初始化随机种子
    
        cout << "正在生成 P8813 [CSP-J 2022] 乘方 测试数据..." << endl;
    
        for (int id = 1; id <= 10; id++) {
            string in_file = to_string(id) + ".in";
            string out_file = to_string(id) + ".out";
    
            ofstream fin(in_file);
            ofstream fout(out_file);
    
            long long a, b;
    
            // --- 针对不同测试点设计数据 ---
            
            if (id == 1) {
                // 测试点 1 (10%): b = 1, 普通情况
                a = 12345; 
                b = 1;
            } 
            else if (id == 2) {
                // 测试点 2 (10%): b = 1, 边界情况
                // 结果恰好是 10^9
                a = 1000000000; 
                b = 1;
            } 
            else if (id == 3) {
                // 测试点 3 (30%): b <= 2, 不超限
                // 30000^2 = 9*10^8 < 10^9
                a = 30000; 
                b = 2;
            } 
            else if (id == 4) {
                // 测试点 4 (30%): b <= 2, 超限
                // 40000^2 = 1.6*10^9 > 10^9 -> -1
                a = 40000; 
                b = 2;
            } 
            else if (id == 5) {
                // 测试点 5: 特殊性质 a = 1 (TLE 陷阱)
                // 如果不特判,循环 10^9 次会超时
                a = 1; 
                b = 1000000000;
            } 
            else if (id == 6) {
                // 测试点 6: 边界测试,结果恰好等于 10^9
                // 10^9 = 1000000000, 应该输出原数
                a = 10; 
                b = 9;
            } 
            else if (id == 7) {
                // 测试点 7: 边界测试,结果刚刚超过 10^9
                // 2^29 < 10^9, 2^30 > 10^9
                // 2^30 = 1073741824 -> -1
                a = 2; 
                b = 30;
            } 
            else if (id == 8) {
                // 测试点 8: 极限数据 (100%)
                // 显然超限 -> -1
                a = 1000000000; 
                b = 1000000000;
            } 
            else if (id == 9) {
                // 测试点 9: 随机大数
                a = rand() % 1000 + 1000; // 1000~1999
                b = rand() % 1000 + 1000; // 指数也很大
            } 
            else {
                // 测试点 10: 2 的幂次溢出 int 范围测试
                // 2^31 > int_max,测试是否用了 long long
                a = 2;
                b = 32;
            }
    
            // --- 写入输入文件 ---
            fin << a << " " << b << endl;
    
            // --- 计算答案并写入输出文件 ---
            long long result = solve(a, b);
            fout << result << endl;
    
            // 关闭文件流
            fin.close();
            fout.close();
    
            cout << "Generated Case " << id << ": a=" << a << ", b=" << b << " -> Ans: " << result << endl;
        }
    
        cout << "全部数据生成完毕!" << endl;
        return 0;
    }
    

    数据设计详解 (覆盖考点)

    1. Case 1-2: 针对 b=1b=1 的情况。
      • Case 1 是普通小数字。
      • Case 2 是边界 a=109a=10^9,验证程序是否能正确输出 10910^9(题目要求 >109>10^9 才输出 -1,=109=10^9 应该输出原数)。
    2. Case 3-4: 针对 b2b \le 2 的情况。
      • Case 3 结果小于 10910^9
      • Case 4 结果大于 10910^9,测试基础判断逻辑。
    3. Case 5: 经典的 TLE 陷阱
      • a=1,b=109a=1, b=10^9。如果选手的程序没有特判 a=1a=1 且去跑循环,绝对超时。
    4. Case 6-7: 数值边界
      • Case 6: 10910^9,刚好压线,应输出 10910^9
      • Case 7: 23010.72^{30} \approx 10.7 亿,刚刚超过 10910^9,测试剪枝是否准确。
    5. Case 10: 数据类型溢出
      • 2322^{32} 超过了 int 的最大值 (23112^{31}-1)。如果选手虽然写了剪枝,但中间变量 ans 用的 int,这里就会计算错误(变成负数或 0),从而无法触发 > 1e9 的判断。这也验证了必须用 long long
    6. Case 8-9: 极限与随机大数,确保护展性。

    使用这个生成器,你可以直接为 OJ 创建一套高质量的测试数据。

    • 0
      @ 2025-11-28 11:42:47

      这是一份标准的 AC 代码。

      代码核心逻辑

      1. 特判 a=1a=1:避免 bb 很大时循环超时。
      2. 暴力循环 + 剪枝:当 a2a \ge 2 时,循环最多执行约 30 次就会超过 10910^9,所以不需要快速幂,直接循环并判断即可。
      3. 防溢出:变量使用 long long,并在乘法后立即检查是否超过阈值。

      标准 C++ 代码

      #include <iostream>
      
      using namespace std;
      
      int main() {
          // 1. 定义变量,必须使用 long long 防止计算过程中爆 int
          long long a, b;
          cin >> a >> b;
      
          // 2. 特判 a = 1 的情况
          // 如果 a 是 1,无论 b 是多少,结果都是 1
          // 如果不写这一步,当 a=1, b=10^9 时循环会跑 10亿次导致 TLE
          if (a == 1) {
              cout << 1 << endl;
              return 0;
          }
      
          // 3. 循环计算
          long long ans = 1;
          for (int i = 1; i <= b; i++) {
              // 累乘
              ans = ans * a;
              
              // 4. 关键剪枝:每次乘完立刻检查
              // 如果超过 10^9 (1e9),直接输出 -1 并结束
              // 因为 a >= 2,这个循环最多执行 30 次左右就会触发 break,非常快
              if (ans > 1000000000) {
                  cout << -1 << endl;
                  return 0;
              }
          }
      
          // 5. 如果循环正常结束,说明结果没有超过 10^9
          cout << ans << endl;
      
          return 0;
      }
      

      常见疑问解答

      • 为什么不用 pow(a, b) pow 函数返回的是 double 类型,当数字很大时会出现科学计数法表示,导致精度丢失(例如个位数不准)。且不易判断准确的整数溢出。
      • 为什么不用快速幂? 快速幂适用于 bb 很大且结果需要取模的情况。但这道题有 10910^9 的上限截断,只要 a2a \ge 2,循环次数极少(常数级),朴素循环配合 break 反而更简单、更不容易写错。
      • 0
        @ 2025-11-28 11:41:04

        你好!我是你的 OI 教练。

        这道题(CSP-J 2022 T1)虽然是当年的签到题,但它是典型的 “坑题”,主要考察你对数据范围的敏感度和对溢出的处理。

        很多同学看到求 aba^b 就直接写个 for 循环或者直接用 pow 函数,结果要么超时(TLE),要么答案错误(WA)。

        下面我给你提供几个关键的解题线索:

        1. 最大的陷阱:超时 (TLE)

        题目中 bb 最大可以达到 10910^9(十亿)。

        • 错误想法:写一个 for 循环,执行 bb 次乘法。
        • 分析:如果输入 1 1000000000(即 1110910^9 次方),你的循环会跑 10 亿次,肯定会超时(通常计算机 1 秒只能跑 1 亿次左右)。
        • 提示:你需要对 a=1a=1 的情况单独写一个 if 判断。如果 a=1a=1,不管 bb 是多少,答案都是 1。

        2. 核心逻辑:真的需要快速幂吗?

        除了 a=1a=1 的情况,当 a2a \ge 2 时,虽然 bb 依然可能很大,但其实你不需要跑完整个循环。

        • 数学直觉23010.72^{30} \approx 10.7 亿,已经超过题目的限制 10910^9 了。
        • 结论:只要 a2a \ge 2,你的循环最多只需要执行 30 次左右,数值就会超过 10910^9
        • 做法:在循环中,每乘一次 aa,立刻检查结果是否超过 10910^9。如果超过,直接输出 -1 并结束程序。这样循环次数极少,完全不用担心超时,也不需要写复杂的快速幂算法。

        3. 数据类型:防爆 (long long)

        题目比较的界限是 10910^9

        • 虽然 10910^9 可以存进 int,但在计算过程中,你的累乘变量 ans 可能会瞬间变得很大。
        • 比如当 ans 接近 10910^9,再乘一个 aa(假设 aa 也是 10910^9),结果会变成 101810^{18}
        • 风险101810^{18} 远远超过了 int 的范围(约 2×1092 \times 10^9),会导致数据溢出变成负数。
        • 对策:你的计算变量 ans 必须定义为 long long

        4. 代码逻辑框架

        你可以参考这个伪代码结构:

        1. 定义 long long 类型的 a, b, ans = 1;
        2. 输入 a, b
        3. 【特判】:如果 a == 1,直接输出 1,结束程序。
        4. 循环 i 从 1 到 b:
            a. ans = ans * a;
            b. 【关键检查】:如果 ans > 10^9 (即 1000000000):
                输出 -1
                结束程序 (return 0)
        5. 如果循环正常结束了(说明没超),输出 ans。
        

        加油!只要处理好特判和溢出判断,这道题就很简单了。试着自己写一下吧!

        • 1

        信息

        ID
        12648
        时间
        1000ms
        内存
        32MiB
        难度
        1
        标签
        递交数
        1
        已通过
        1
        上传者