2 条题解
-
0
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
一、 思路提示
- 有序化:
- 等差数列的定义是基于“顺序”的。给出的数组是乱序的,第一步必须排序(Sort),设排序后的数组为 。
- 寻找公差 :
- 排序后,我们计算相邻两项的差值:,。
- 原本的公差 必须是所有 的公约数。
- 题目要求“项数最少” 这意味着公差 要尽可能大(步子迈得越大,走完同样距离所需的步数越少)。
- 所以,目标公差 就是所有相邻差值的最大公约数(Greatest Common Divisor, GCD)。
- 特殊情况:
- 如果所有数字都一样(比如
5 5 5),差值全为 0。GCD(0, 0) 通常无定义或为 0。在这种情况下,公差 ,项数就是 。
- 如果所有数字都一样(比如
- 公式计算:
- 求出 后,项数 。
二、 预备知识点总结
- 排序:
std::sort,复杂度 。 - 最大公约数 (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。
- 欧几里得算法(辗转相除法):
- 数学逻辑:等差数列通项公式的逆运算。
三、 启发式教学:草稿纸上的推理过程
教练:“同学们,假设你在地上捡到了一把断掉的尺子,上面只剩下几个刻度:30, 45, 60。请问这把尺子原本最小的刻度单位是多少?”
- 排序:已经是有序的了:30, 45, 60。
- 看间距:
- 30 到 45,距离 15。
- 45 到 60,距离 15。
- “看来刻度单位是 15?这把尺子可能是 30, 45, 60。”
教练:“那如果捡到的刻度是 3, 9, 15, 23 呢?哦,23不是等差的,换一个:2, 6, 12。”
- 排序:2, 6, 12。
- 看间距:
- 2 到 6,距离 4。
- 6 到 12,距离 6。
- “刻度单位可以是 4 吗?”
- “不行,如果是 4,6 后面应该是 10,然后 14。得不到 12。”
- “刻度单位可以是 6 吗?”
- “不行,如果是 6,2 后面应该是 8。得不到 6。”
- “那这个单位(公差)必须既能走完 4,又能走完 6。”
- 结论:公差必须是 4 和 6 的公约数。要项数最少 最大公约数 GCD(4, 6) = 2。
- 验证:
- 以 2 为单位:2, 4, 6, 8, 10, 12。
- 包含题目给的 2, 6, 12。没问题。
- 项数 = 。
教练:“所以,我们的算法就是:先排序,求所有相邻差值,算出它们的最大公约数,最后套公式。”
四、 读题关键词总结
- “等差数列” 核心模型。
- “项数最少” 意味着公差 最大 求 GCD。
- “输入乱序” 必须 Sort。
- “正整数” / 使用
int足够(差值最大 ),但计算结果可能稍大(虽然本题项数不会超过 ,但在中间计算时注意溢出风险,不过本题不用担心乘法)。
五、 标准题解代码 (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; }时间与空间复杂度分析
- 时间复杂度:
- 排序:。
- 遍历求 GCD: 次 GCD 操作。每次 GCD 是 。总计 。
- 总时间:。
- 空间复杂度:
- 存储数组:。
六、 数据生成器 (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
- 上传者