题解:洛谷 P1009 阶乘之和
【题目来源】
洛谷:P1009 [NOIP 1998 普及组] 阶乘之和 - 洛谷
【题目描述】
用高精度计算出 \(S=1!+2!+3!+\dots+n!(n\le 50)\)。
其中 ! 表示阶乘,定义为 \(n!=n\times(n-1)\times (n-2)\times \dots \times 1\)。例如,\(5!=5\times 4\times 3\times 2\times 1=120\)。
【输入】
一个正整数 \(n\)。
【输出】
一个正整数 \(S\),表示计算结果。
【输入样例】
3
【输出样例】
9
【核心思想】
-
问题分析:给定 \(n \le 50\),计算 \(S = 1! + 2! + \cdots + n!\)。由于 \(50!\) 远超 64 位整数范围,必须使用高精度运算(大整数模拟)。
-
算法选择:
- 高精度乘法:用数组倒序存储大整数,模拟竖式乘法计算阶乘
- 高精度加法:逐位累加每个阶乘到结果数组
- 倒序存储:数组下标 \(0\) 存个位,便于进位处理
-
关键步骤:
- 读入数据:
cin >> n - 初始化:
a[0] = 1, res[0] = 1(\(1! = 1\)) - 循环计算(\(i\) 从 \(2\) 到 \(n\)):
- 计算 \(i!\)(高精度乘法 \(a \times i\)):
- 遍历 \(j\) 从 \(0\) 到 \(1010\):
a[j] = carry + a[j] * i:当前位乘 \(i\) 加进位carry = a[j] / 10:计算新进位a[j] %= 10:保留个位
- 遍历 \(j\) 从 \(0\) 到 \(1010\):
- 累加阶乘到结果(高精度加法 \(res += a\)):
- 遍历 \(j\) 从 \(0\) 到 \(1010\):
res[j] += a[j]:逐位相加res[j+1] += res[j] / 10:处理进位res[j] %= 10:保留个位
- 遍历 \(j\) 从 \(0\) 到 \(1010\):
- 计算 \(i!\)(高精度乘法 \(a \times i\)):
- 去除前导零:从高位向低位找第一个非零数字确定长度
- 倒序输出:从高位到低位输出结果
- 读入数据:
-
时间/空间复杂度:
- 时间复杂度:\(O(n \cdot L)\),\(L = 1010\) 为数组长度,每次乘法和加法均为 \(O(L)\)
- 空间复杂度:\(O(L)\),两个高精度数组
-
高精度运算的核心思想:
- 数组模拟大整数:用数组每一位存一个十进制数字,下标 \(0\) 为个位,便于从低位到高位处理进位
- 逐位乘加:模拟小学竖式运算,当前位 = 当前位 \(\times\) 乘数 + 进位,新进位 = 当前位 / 10
- 即时进位:乘法和加法后立即处理进位,避免溢出
- 结果复用:\(a\) 数组保存当前 \(i!\),下次循环直接乘 \((i+1)\) 得 \((i+1)!\),无需重新计算
- 适用于大整数阶乘、高精度累加、数位模拟类问题
【算法标签】
普及- #数学
【代码详解】
#include <bits/stdc++.h> // 包含标准库头文件(万能头文件)
using namespace std; // 使用标准命名空间
// 定义全局数组存储阶乘和结果
int a[1010]; // 存储当前阶乘值(倒序存储)
int res[1010]; // 存储阶乘累加和(倒序存储)
/**
* 主函数 - 程序入口
* @return 程序执行状态码(0表示成功)
*/
int main()
{
int n; // 定义变量:计算到n的阶乘和
cin >> n; // 输入n值
// 初始化数组(1! = 1)
a[0] = 1; // 个位存储1
res[0] = 1; // 初始和为1
// 计算2!到n!的阶乘及其累加和
for (int i = 2; i <= n; i++)
{
// 计算i的阶乘(a = a * i)
int carry = 0; // 进位值初始化为0
for (int j = 0; j < 1010; j++)
{
a[j] = carry + a[j] * i; // 当前位乘i加上进位
carry = a[j] / 10; // 计算新的进位
a[j] %= 10; // 保留个位数
}
// 计算1!+2!+...+i!(res += a)
for (int j = 0; j < 1010; j++)
{
res[j] += a[j]; // 逐位相加
res[j+1] += res[j] / 10; // 处理进位
res[j] %= 10; // 保留个位数
}
}
// 计算实际数字长度(去除前导零)
int len = 1010;
while (0 == res[len-1] && len > 1)
{
len--; // 从高位向低位查找第一个非零数字
}
// 倒序输出结果(高位在前)
for (int i = len-1; i >= 0; i--)
{
cout << res[i]; // 输出每一位数字
}
return 0; // 程序正常结束
}
【运行结果】
3
9
浙公网安备 33010602011771号