Bootstrap

洛谷 P1009 [NOIP 1998 普及组] 阶乘之和

通常思路

先求 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;
}