2 条题解

  • 0
    @ 2025-12-12 18:03:30

    NOI的C++14标准允许直接调用__gcd库吗?

    在 C++14 标准下,关于 __gcd 的情况如下:

    结论:

    __gcd 并不是 C++14 的标准函数,但它在 NOIP/CSP/GESP 等竞赛常用的 GCC 编译器(g++)中可以使用。

    以下是详细说明:

    1. 它的来源

    • 非标准__gcd 是 GNU C++ 标准库(libstdc++)的一个私有扩展函数(这就是为什么它前面有两个下划线)。它不属于 C++14 的官方 ISO 标准。
    • 头文件:在 GCC/MinGW 编译器中,它位于 <algorithm> 头文件中。

    2. 在竞赛中的可用性

    • NOIP/CSP/GESP/OI:这些比赛通常使用 GCC (g++) 编译器。因此,在比赛中是可以直接使用 __gcd(a, b),只要引用了 <algorithm> 头文件即可。
    • 风险:如果你换用了其他编译器(比如微软的 MSVC),或者将来比赛环境严格限制非标准扩展,这个函数就会报错。

    3. C++ 标准库的正式支持

    • C++17:从 C++17 开始,官方终于加入了标准的 GCD 函数,叫做 std::gcd
      • 头文件:<numeric>
      • 用法:std::gcd(a, b)
    • C++14:官方标准库里没有 GCD 函数。

    4. 建议方案(如何在 C++14 中稳健地求最大公约数)

    为了代码的可移植性符合 C++14 标准(也为了防止评测环境差异),建议选手在 C++14 环境下手写一个 gcd 函数。它非常短,只需一行代码。

    推荐的手写写法(欧几里得算法):

    // C++14 标准写法,不依赖特定编译器
    int gcd(int a, int b) {
        return b == 0 ? a : gcd(b, a % b);
    }
    

    总结对比表:

    函数名 标准归属 头文件 是否推荐在 C++14 比赛用 备注
    __gcd(a, b) GCC 扩展 (非标准) <algorithm> 可用,但不严谨 只能在 g++/clang 下用,MSVC 不行。
    std::gcd(a, b) C++17 标准 <numeric> 不可用 C++14 还没这个函数。
    gcd(a, b) 手写函数 最推荐 此时考察的是你的算法基础,且绝对安全。

    对于 GESP 考级或 CSP 比赛: 如果你嫌麻烦,且确定编译器是 g++,用 __gcd 没问题;但为了养成好习惯,建议花 5 秒钟手写一个 gcd 函数。

          d = my_gcd(d, diff);//自己用辗转相除法手写方法
          d=__gcd(d,diff);//使用algorithm库内置的方法
    
    • 0
      @ 2025-12-12 17:58:00

      一、 思路提示

      1. 有序化
        • 等差数列的定义是基于“顺序”的。给出的数组是乱序的,第一步必须排序(Sort),设排序后的数组为 AA
      2. 寻找公差 dd
        • 排序后,我们计算相邻两项的差值:Δ1=A2A1\Delta_1 = A_2 - A_1Δ2=A3A2, \Delta_2 = A_3 - A_2, \dots
        • 原本的公差 dd 必须是所有 Δi\Delta_i公约数
        • 题目要求“项数最少” \rightarrow 这意味着公差 dd尽可能大(步子迈得越大,走完同样距离所需的步数越少)。
        • 所以,目标公差 dd 就是所有相邻差值的最大公约数(Greatest Common Divisor, GCD)
      3. 特殊情况
        • 如果所有数字都一样(比如 5 5 5),差值全为 0。GCD(0, 0) 通常无定义或为 0。在这种情况下,公差 d=0d=0,项数就是 NN
      4. 公式计算
        • 求出 dd 后,项数 =ANA1d+1= \frac{A_N - A_1}{d} + 1

      二、 预备知识点总结

      1. 排序std::sort,复杂度 O(NlogN)O(N \log N)
      2. 最大公约数 (GCD)
        • 欧几里得算法(辗转相除法):gcd(a, b) = gcd(b, a % b)
        • 多个数的 GCD:gcd(a, b, c) = gcd(a, gcd(b, c))
        • C++14/17 标准库内置 std::__gcd<numeric> 中的 std::gcd
      3. 数学逻辑:等差数列通项公式的逆运算。

      三、 启发式教学:草稿纸上的推理过程

      教练:“同学们,假设你在地上捡到了一把断掉的尺子,上面只剩下几个刻度:30, 45, 60。请问这把尺子原本最小的刻度单位是多少?”

      1. 排序:已经是有序的了:30, 45, 60。
      2. 看间距
        • 30 到 45,距离 15。
        • 45 到 60,距离 15。
        • “看来刻度单位是 15?这把尺子可能是 30, 45, 60。”

      教练:“那如果捡到的刻度是 3, 9, 15, 23 呢?哦,23不是等差的,换一个:2, 6, 12。”

      1. 排序:2, 6, 12。
      2. 看间距
        • 2 到 6,距离 4。
        • 6 到 12,距离 6。
        • “刻度单位可以是 4 吗?”
        • “不行,如果是 4,6 后面应该是 10,然后 14。得不到 12。”
        • “刻度单位可以是 6 吗?”
        • “不行,如果是 6,2 后面应该是 8。得不到 6。”
        • “那这个单位(公差)必须既能走完 4,又能走完 6。”
        • 结论:公差必须是 4 和 6 的公约数。要项数最少 \rightarrow 最大公约数 GCD(4, 6) = 2。
      3. 验证
        • 以 2 为单位:2, 4, 6, 8, 10, 12。
        • 包含题目给的 2, 6, 12。没问题。
        • 项数 = (122)/2+1=6(12 - 2) / 2 + 1 = 6

      教练:“所以,我们的算法就是:先排序,求所有相邻差值,算出它们的最大公约数,最后套公式。”


      四、 读题关键词总结

      1. “等差数列” \rightarrow 核心模型。
      2. “项数最少” \rightarrow 意味着公差 dd 最大 \rightarrow 求 GCD。
      3. “输入乱序” \rightarrow 必须 Sort。
      4. “正整数” / 10910^9 \rightarrow 使用 int 足够(差值最大 10910^9),但计算结果可能稍大(虽然本题项数不会超过 10910^9,但在中间计算时注意溢出风险,不过本题不用担心乘法)。

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

      /**
       * 题目:残缺的等差数列 (The Broken Arithmetic Sequence)
       * 算法:排序 + 最大公约数 (Sorting + GCD)
       * 难度:GESP 5级 / CSP-J T2
       */
      
      #include <iostream>
      #include <vector>
      #include <algorithm>
      #include <numeric> // C++17 包含 std::gcd,但在竞赛中常手写或用 __gcd
      
      using namespace std;
      
      // 手写 GCD 函数 (辗转相除法)
      int my_gcd(int a, int b) {
          while (b != 0) {
              int temp = a % b;
              a = b;
              b = temp;
          }
          return a;
      }
      
      int main() {
          // 1. I/O 加速
          ios_base::sync_with_stdio(false);
          cin.tie(NULL);
      
          int N;
          if (!(cin >> N)) return 0;
      
          vector<int> A(N);
          for (int i = 0; i < N; ++i) {
              cin >> A[i];
          }
      
          // 2. 排序
          sort(A.begin(), A.end());
      
          // 3. 特判:如果所有数都相同 (首项 == 末项)
          if (A[0] == A[N - 1]) {
              cout << N << endl;
              return 0;
          }
      
          // 4. 计算所有相邻差值的 GCD
          // 先计算第一个差值作为初始 d
          int d = A[1] - A[0];
          
          for (int i = 2; i < N; ++i) {
              int diff = A[i] - A[i - 1];
              d = my_gcd(d, diff);
          }
      
          // 5. 计算项数
          // 如果 d 变成了 0 (理论上前面特判了不会发生,但在逻辑上要严谨)
          if (d == 0) {
              cout << N << endl;
          } else {
              // 公式:(末项 - 首项) / 公差 + 1
              int count = (A[N - 1] - A[0]) / d + 1;
              cout << count << endl;
          }
      
          return 0;
      }
      

      时间与空间复杂度分析

      • 时间复杂度
        • 排序:O(NlogN)O(N \log N)
        • 遍历求 GCD:NN 次 GCD 操作。每次 GCD 是 O(log(Value))O(\log(\text{Value}))。总计 O(NlogV)O(N \log V)
        • 总时间:O(NlogN)O(N \log N)
      • 空间复杂度
        • 存储数组:O(N)O(N)

      六、 数据生成器 (Generator.cpp)

      /**
       * 题目:残缺的等差数列
       * 数据生成器
       */
      
      #include <iostream>
      #include <fstream>
      #include <vector>
      #include <algorithm>
      #include <random>
      #include <ctime>
      #include <set>
      
      using namespace std;
      
      mt19937 rng(time(0));
      
      // 手写 GCD
      int gcd(int a, int b) {
          return b == 0 ? a : gcd(b, a % b);
      }
      
      // 核心解法
      int solve(int N, vector<int> A) {
          sort(A.begin(), A.end());
          if (A[0] == A[N - 1]) return N;
          int d = A[1] - A[0];
          for(size_t i=2; i<A.size(); ++i) d = gcd(d, A[i]-A[i-1]);
          if (d == 0) return N;
          return (A[N-1] - A[0]) / d + 1;
      }
      
      // 随机生成器
      int rand_int(int min_v, int max_v) {
          uniform_int_distribution<int> dist(min_v, max_v);
          return dist(rng);
      }
      
      void make_case(int id) {
          int N;
          vector<int> A;
      
          switch(id) {
              case 1: // 样例
                  N = 5; A = {2, 6, 4, 10, 20};
                  break;
              case 2: // 已经是一个完整的等差数列
                  N = 10; 
                  for(int i=0; i<N; ++i) A.push_back(1 + i*3);
                  break;
              case 3: // 所有数相同
                  N = 20; 
                  for(int i=0; i<N; ++i) A.push_back(100);
                  break;
              case 4: // 两个数 (互质差)
                  N = 2;
                  A = {10, 13}; // d=3
                  break;
              case 5: // 两个数 (大公约数)
                  N = 2;
                  A = {10, 100}; // d=90
                  break;
              case 6: // 随机小数据,公差为 1
                  N = 100;
                  {
                      set<int> s;
                      while(s.size() < N) s.insert(rand_int(1, 500));
                      A.assign(s.begin(), s.end());
                  }
                  break;
              case 7: // 随机中数据,公差随机
                  N = 1000;
                  {
                      int start = rand_int(1, 1000);
                      int d = rand_int(2, 100);
                      set<int> s;
                      // 从一个大数列中随机挖取
                      vector<int> full;
                      for(int i=0; i<10000; ++i) full.push_back(start + i*d);
                      shuffle(full.begin(), full.end(), rng);
                      for(int i=0; i<N; ++i) A.push_back(full[i]);
                  }
                  break;
              case 8: // 差值互质,导致 d=1
                  N = 5;
                  // 2, 5 (diff 3), 7 (diff 2) -> gcd(3,2)=1
                  A = {2, 5, 7, 100, 200}; 
                  break;
              case 9: // 大数据,数值大
                  N = 100000;
                  {
                      int start = rand_int(1, 1000);
                      int d = rand_int(100, 1000);
                      // 构造
                      A.push_back(start);
                      for(int i=1; i<N; ++i) {
                          // 随机跳跃步数
                          int jump = rand_int(1, 10);
                          A.push_back(A.back() + jump * d);
                      }
                      // 打乱
                      shuffle(A.begin(), A.end(), rng);
                  }
                  break;
              case 10: // 大数据,稀疏
                  N = 100000;
                  A.push_back(0);
                  A.push_back(1000000000); // 差 10^9
                  for(int i=0; i<N-2; ++i) A.push_back(rand_int(1, 999999999));
                  // 这种情况下 gcd 很大概率是 1
                  break;
          }
      
          // 写入 .in
          string in_file = to_string(id) + ".in";
          ofstream fout_in(in_file);
          fout_in << N << "\n";
          // 随机打乱输入顺序 (题目说可能是乱序)
          shuffle(A.begin(), A.end(), rng);
          for(int i=0; i<N; ++i) fout_in << A[i] << (i==N-1?"":" ");
          fout_in << "\n";
          fout_in.close();
      
          // 写入 .out
          string out_file = to_string(id) + ".out";
          ofstream fout_out(out_file);
          fout_out << solve(N, A) << "\n";
          fout_out.close();
      
          cout << "Generated Case " << id << endl;
      }
      
      int main() {
          ios_base::sync_with_stdio(false);
          for(int i=1; i<=10; ++i) make_case(i);
          return 0;
      }
      

      这道题目将**初中代数(等差数列)数论算法(GCD)**完美结合。虽然代码量不大,但逻辑性强,要求学生理解“差分的公约数即为原公差”这一数学本质,非常适合 GESP 5 级的考察目标。

      • 1

      信息

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