用C++递归搞定分数求和:从《信息学奥赛一本通》1209题看通分与约分的实战技巧

张开发
2026/6/11 11:11:54 15 分钟阅读

分享文章

用C++递归搞定分数求和:从《信息学奥赛一本通》1209题看通分与约分的实战技巧
递归算法实战从分数求和问题看信息学奥赛的解题思维在信息学奥赛的备战过程中很多初学者往往会被看似复杂的数学计算问题难住。分数求和就是这样一个典型例子——它既考察了对基础数学概念的理解又检验了将数学思维转化为代码实现的能力。今天我们就以《信息学奥赛一本通》中的1209题为例深入剖析如何用递归思想解决分数求和问题。1. 问题分析与数学建模分数求和问题的核心在于理解通分和约分这两个关键步骤。给定n个分数我们需要将它们相加并化简为最简形式。这看似简单但其中蕴含着几个需要解决的子问题通分计算找到所有分数的公分母并将分子相加约分处理将结果化简为最简分数形式输出格式化根据分母是否为1决定输出整数还是分数形式在数学上这个过程可以表示为sum a1/b1 a2/b2 ... an/bn (a1*LCM/b1 a2*LCM/b2 ... an*LCM/bn)/LCM (sum of numerators)/LCM其中LCM是所有分母的最小公倍数。但实际上我们可以采用更高效的方法逐步通分。即每次将当前累加结果与下一个分数相加时直接计算新的分子和分母new_numerator current_numerator * next_denominator next_numerator * current_denominator new_denominator current_denominator * next_denominator这种方法避免了预先计算所有分母的LCM更加高效。2. 递归算法的核心辗转相除法约分过程的核心是找到分子和分母的最大公约数(GCD)。这里我们采用著名的欧几里得算法辗转相除法它天然适合用递归实现int gcd(int p, int q) { if (q 0) return p; return gcd(q, p % q); }这个简洁的递归函数体现了算法之美基准情况当q为0时p就是GCD递归情况GCD(p,q) GCD(q, p%q)让我们通过一个例子理解其工作原理。计算GCD(48,18)GCD(48,18) → 18≠0计算GCD(18,48%1812)GCD(18,12) → 12≠0计算GCD(12,18%126)GCD(12,6) → 6≠0计算GCD(6,12%60)GCD(6,0) → q0返回6这个例子展示了递归如何将问题逐步简化直到达到可以直接解决的基准情况。3. 完整解决方案的实现结合通分和约分的思路我们可以构建完整的解决方案。以下是详细的代码实现和解析#include iostream using namespace std; // 递归计算最大公约数 int gcd(int p, int q) { return q 0 ? p : gcd(q, p % q); } int main() { int n; // 分数个数 cin n; int a 0; // 累加结果的分子初始为0 int b 1; // 累加结果的分母初始为1 for (int i 0; i n; i) { int x, y; // 新分数的分子和分母 char slash; // 用于读取/字符 cin x slash y; // 通分计算 int new_a a * y x * b; int new_b b * y; // 约分处理 int d gcd(new_a, new_b); a new_a / d; b new_b / d; } // 输出结果 if (b 1) { cout a; // 整数形式输出 } else { cout a / b; // 分数形式输出 } return 0; }代码中的几个关键点初始化累加结果初始化为0/1即0循环处理每个分数读取并解析分数格式为p/q通分计算新分子和分母立即约分保持结果最简输出格式化根据分母是否为1决定输出格式注意在每次通分后立即约分可以防止分子分母数值过大导致溢出。这是处理分数运算时的一个重要技巧。4. 算法优化与边界情况处理虽然上述解决方案已经能够正确解决问题但在实际竞赛中我们还需要考虑一些优化和边界情况4.1 防止整数溢出当处理多个分数或分子分母较大时乘积可能导致整数溢出。我们可以通过以下方式优化提前约分在通分前先约分当前结果和新分数使用更大数据类型如long long代替int更频繁的约分在计算过程中多次约分改进后的通分和约分逻辑// 在读取新分数后 // 1. 先约分当前结果 int d1 gcd(a, b); a / d1; b / d1; // 2. 约分新分数 int d2 gcd(x, y); x / d2; y / d2; // 3. 通分计算 int new_a a * y x * b; int new_b b * y; // 4. 再次约分 int d3 gcd(new_a, new_b); a new_a / d3; b new_b / d3;4.2 处理负分数虽然题目说明中规定分子分母不为负但在更一般的情况下我们需要处理负分数。解决方案统一符号处理保持分母为正符号由分子决定GCD计算对负数取绝对值修改后的gcd函数int gcd(int p, int q) { p abs(p); q abs(q); return q 0 ? p : gcd(q, p % q); }4.3 输入验证健壮的程序应该处理各种异常输入// 验证分数有效性 if (y 0) { cerr 错误分母不能为零 endl; return 1; } if (x 0 || y 0) { cerr 错误分子分母必须为正数 endl; return 1; }5. 递归思想的扩展应用通过这个分数求和问题我们可以看到递归在算法设计中的强大之处。递归思想还可以应用于许多其他场景5.1 分形图形绘制递归非常适合绘制分形图形如科赫雪花、谢尔宾斯基三角形等。这些图形具有自相似性可以通过不断分解为更小的相似子问题来实现。5.2 树形结构遍历在处理二叉树或其他树形数据结构时递归提供了最直观的遍历方式void inorderTraversal(TreeNode* root) { if (root nullptr) return; inorderTraversal(root-left); cout root-val ; inorderTraversal(root-right); }5.3 回溯算法许多组合问题如八皇后、数独求解都可以用递归回溯法优雅解决bool solveSudoku(vectorvectorchar board) { for (int i 0; i 9; i) { for (int j 0; j 9; j) { if (board[i][j] .) { for (char c 1; c 9; c) { if (isValid(board, i, j, c)) { board[i][j] c; if (solveSudoku(board)) return true; board[i][j] .; // 回溯 } } return false; } } } return true; }5.4 动态规划问题许多动态规划问题可以用递归加记忆化的方式实现如斐波那契数列unordered_mapint, int memo; int fibonacci(int n) { if (n 1) return n; if (memo.find(n) ! memo.end()) return memo[n]; return memo[n] fibonacci(n-1) fibonacci(n-2); }6. 从解题到竞赛构建算法思维信息学奥赛不仅考察编程能力更注重算法思维。通过这个分数求和问题我们可以总结出以下解题方法论问题分解将复杂问题拆解为多个可解决的子问题数学建模用数学语言描述问题和解决方案算法选择根据问题特点选择合适的算法范式代码实现将算法精确转化为代码测试验证考虑各种边界情况和极端输入在实际竞赛中建议采用以下练习方法刻意练习针对薄弱环节进行专项训练错题分析深入理解每个错误背后的原因时间管理模拟真实比赛环境进行练习代码简洁性追求逻辑清晰而非代码简短递归作为一种强大的编程范式其思维方式与数学归纳法高度一致。掌握递归不仅能够解决特定类型的问题更能培养抽象思维和问题分解能力这对信息学竞赛和未来的编程工作都至关重要。

更多文章