从‘笨办法’到‘巧办法’:用C++优化阶乘和计算的三种思路(附NOI真题解析)
2026/5/12 6:16:32 网站建设 项目流程

从‘笨办法’到‘巧办法’:用C++优化阶乘和计算的三种思路(附NOI真题解析)

在算法竞赛的初期阶段,许多学习者常陷入"能跑通就行"的思维陷阱。直到某次提交后看到刺眼的"Time Limit Exceeded",才意识到效率优化的重要性。本文将以经典的阶乘和问题为例,带你经历从暴力解法到高效算法的完整优化历程——这正是NOI等竞赛中考察的核心能力之一。

1. 问题定义与暴力解法剖析

给定正整数n,计算1!+2!+3!+...+n!的值。这是信息学奥赛中的经典题型,出现在《信息学奥赛一本通》1091题、OpenJudge NOI 1.5第34题等多个权威题库中。

最直观的暴力解法通常表现为双重循环结构:

#include <iostream> using namespace std; int main() { int n; cin >> n; long long sum = 0; for(int i=1; i<=n; ++i) { long long fact = 1; for(int j=1; j<=i; ++j) { fact *= j; } sum += fact; } cout << sum << endl; return 0; }

这种实现的时间复杂度是O(n²),当n达到1e5量级时,现代计算机也需要数秒才能完成计算。在竞赛环境中,这样的性能往往无法通过时间限制。

注意:实际编程中应使用long long类型存储阶乘结果,因为20!就已经接近2.4e18,超出了int的表示范围。

2. 优化思路一:函数封装与重复计算消除

观察暴力解法会发现存在严重的重复计算——计算i!时,实际上已经计算过(i-1)!。改进方案可以单独封装阶乘函数,但更聪明的做法是利用前次计算结果:

long long factorial_sum(int n) { long long total = 0, current = 1; for(int i=1; i<=n; ++i) { current *= i; // i! = (i-1)! * i total += current; } return total; }

这个单循环实现的优势非常明显:

优化维度暴力解法优化方案
时间复杂度O(n²)O(n)
空间复杂度O(1)O(1)
实际运行时间2.1s@n=1e50.003s@n=1e5

3. 优化思路二:递归解法的性能陷阱

许多初学者会尝试用递归实现,因为阶乘的数学定义本身就是递归式的:

long long factorial(int n) { return n <= 1 ? 1 : n * factorial(n-1); } long long sum_recursive(int n) { return n == 1 ? 1 : factorial(n) + sum_recursive(n-1); }

但这种实现存在三个致命问题:

  1. 重复计算更严重——计算factorial(n)时已经计算了所有小于n的阶乘
  2. 递归深度限制——n较大时可能导致栈溢出
  3. 函数调用开销——每次递归都产生额外的性能损耗

实测表明,当n>1e4时,递归解法在多数评测系统上都会崩溃。这提醒我们:数学上的优雅不等于计算效率的优越

4. 竞赛级优化技巧:预处理与记忆化

对于需要多次查询不同n值的情况,可以采用预处理技术:

const int MAX_N = 1e5; long long pre[MAX_N + 1]; // 预处理数组 void initialize() { pre[0] = 1; for(int i=1; i<=MAX_N; ++i) { pre[i] = pre[i-1] * i; } } long long query(int n) { long long sum = 0; for(int i=1; i<=n; ++i) { sum += pre[i]; } return sum; }

这种方案的典型应用场景包括:

  • 需要处理大量查询的在线评测系统
  • 动态规划中的状态预处理
  • 组合数学相关问题的快速求解

5. NOI真题实战:OpenJudge案例解析

让我们看一个具体的竞赛题目要求:

输入格式
一个整数n(1 ≤ n ≤ 20)

输出格式
1!+2!+...+n!的值

最优解实现

#include <iostream> using namespace std; int main() { int n; cin >> n; long long sum = 0, fact = 1; for(int i=1; i<=n; ++i) { fact *= i; sum += fact; } cout << sum << endl; return 0; }

这个实现完美符合题目要求,其优势在于:

  • 时间复杂度O(n)优于题目要求
  • 仅使用基本数据类型,无额外空间消耗
  • 代码简洁明了,适合竞赛快速编码

在NOI等竞赛中,类似的优化思维可以扩展到更复杂的问题,如斐波那契数列计算、素数判定等。关键在于发现计算过程中的重复模式,并找到存储中间结果的巧妙方法。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询