通常思路
先求 n 的阶乘,然后累加求和。
完整代码
#include<stdio.h>
int main() {
int n;
unsigned long long sum = 0, factorial = 1;
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
factorial *= i;
sum += factorial; // 求完阶乘即相加
}
printf("%lld", sum);
return 0;
}
但是当 n > 20 时,即便是 long long 类型数据也会溢出!!!,因此采用高精度算法。
使用高精度算法
分析
高精度乘法求阶乘
形参:n,存储阶乘的数组 s,指示数组长度的指针 *len(每次调用函数时修改数组长度)
// 高精度乘法求 n 的阶乘
void Multi(int n, int s[], int *len) {
int carryFlag = 0; // 进位数
for (int i = 0; i < *len; i++) {
s[i] = s[i] * n + carryFlag;
carryFlag = s[i] / 10;
s[i] %= 10;
}
while (carryFlag) { // 剩余进位数
s[*len] = carryFlag % 10;
carryFlag /= 10;
(*len)++;
}
}
高精度加法求阶乘之和
形参: a1 表示计算前后的阶乘之和,a2 表示阶乘,len1 为 a1 数组长度,每次调用函数时修改数组长度,len2 表示 len2 数组长度,长度不需要修改。
*len1 = (*len1 >= len2 ? *len1 : len2) + 1;
比较两数组长度,选取较长数组的长度并 +1,用作结果的长度。
sum = a1[i] + (i < len2 ? a2[i] : 0) + carryFlag;
carryFlag = sum / 10;
a1[i] = sum % 10;
将两个高精度数(以数组形式表示)的第 i 位相加,并加上前一位的进位(carryFlag
)。
具体来说:
a1[i]
表示第一个数(a1
)的第i
位的数字。(i < len2 ? a2[i] : 0)
是一个条件表达式,用于处理两个数长度不同的情况。如果 i 小于第二个数(a2
)的长度(len2
),则取a2[i]
(即第二个数的第i
位数字);否则,取0
(因为第二个数已经没有更多的位数了)。carryFlag
是前一位相加时产生的进位,需要加到当前位的计算中。
以下写法有越界风险:
void Add(int a1[], int *len1, int a2[], int len2) {
int carryFlag = 0;
*len1 = (*len1 >= len2 ? *len1 : len2) + 1;
for (int i = 0; i < *len1; i++) {
a1[i] = a1[i] + a2[i];
a1[i + 1] = a1[i] / 10; // a1[i + 1] 可能会导致数组越界访问,特别是当 i 达到 *len1 - 1 时,a1[i + 1] 会访问到数组外的内存
a1[i] = a1[i] % 10;
}
}
while (*len1 > 1 && a1[*len1 - 1] == 0) {
(*len1)--;
}
*len1 > 1:当 len1 长度为 1 时,即便 a1[0] = 0,也不做删除。
a1[*len1 - 1] == 0:检查数组的最高位(即 a1[*len1 - 1])是否为 0。如果是 0,则说明这一位是前导零,需要去除。
完整函数代码
// 高精度加法
void Add(int a1[], int *len1, int a2[], int len2) {
int carryFlag = 0;
int sum;
*len1 = (*len1 >= len2 ? *len1 : len2) + 1;
for (int i = 0; i < *len1; i++) {
sum = a1[i] + (i < len2 ? a2[i] : 0) + carryFlag;
carryFlag = sum / 10;
a1[i] = sum % 10;
}
while (carryFlag) {
a1[*len1] = carryFlag % 10;
carryFlag /= 10;
(*len1)++;
}
// 去除前导 0
while (*len1 > 1 && a1[*len1 - 1] == 0) {
(*len1)--;
}
}
完整代码
#include<stdio.h>
#define MAX 10000
// 高精度乘法
void Multi(int n, int s[], int *len) {
int carryFlag = 0;
for (int i = 0; i < *len; i++) {
s[i] = s[i] * n + carryFlag;
carryFlag = s[i] / 10;
s[i] %= 10;
}
while (carryFlag) {
s[*len] = carryFlag % 10;
carryFlag /= 10;
(*len)++;
}
}
// 高精度加法
void Add(int a1[], int *len1, int a2[], int len2) {
int carryFlag = 0;
int sum;
*len1 = (*len1 >= len2 ? *len1 : len2) + 1;
for (int i = 0; i < *len1; i++) {
sum = a1[i] + (i < len2 ? a2[i] : 0) + carryFlag;
carryFlag = sum / 10;
a1[i] = sum % 10;
}
while (carryFlag) {
a1[*len1] = carryFlag % 10;
carryFlag /= 10;
(*len1)++;
}
// 去除前导 0
while (*len1 > 1 && a1[*len1 - 1] == 0) {
(*len1)--;
}
}
int main() {
int n;
int sum[MAX] = { 0 }; // 存放阶乘之积
int mul[MAX] = { 0 }; // 存放阶乘
int sumLen = 1, mulLen = 1;
scanf("%d", &n);
mul[0] = 1, sum[0] = 1;
for (int i = 2; i <= n; i++) {
Multi(i, mul, &mulLen);
Add(sum, &sumLen, mul, mulLen);
}
for (int i = sumLen - 1; i >= 0; i--) {
printf("%d", sum[i]);
}
return 0;
}