洛谷P1009(阶乘之和[NOIP 1998 普及组])题解

题目大意:要计算S=1!+2!+…+n!,其中n可以到50。

首先,我们需要想怎么实现高精度的阶乘和加法。高精度通常是用数组或者字符串来模拟大数的每一位,然后手动实现加法和乘法的运算。比如,阶乘的计算可以用一个数组来存储每一位数字,然后每次乘以当前的数,处理进位。而加法的话,同样需要逐位相加,处理进位。

那具体步骤应该是这样的:先算出每个i的阶乘,然后将它们累加起来。每次计算i!的时候,可以用之前的结果乘以i,这样就不用每次都从头开始计算,比如计算5!的时候,可以用4!的结果乘以5。这样会更高效一些。

然后,对于高精度乘法,比如一个数组表示的数和整数i相乘,需要每一位都乘以i,然后处理进位。例如,数组是[2, 1],代表12,乘以3的话,每一位乘3得到[6,3],然后处理进位,变成6和3,也就是36。如果有进位的话,比如[5, 2](25)乘以3,各位得到15和6,然后处理进位,15的十位1进到前面,变成5和6,加上进位后的结果就是75。

接下来,加法部分。每次计算完i!之后,需要将这个结果加到总和里。这里的高精度加法也需要处理进位的问题。例如,总和是[9],加上[1,2](21),得到[9+21=30],即[0,3],也就是30。

那具体怎么实现呢?可能需要用两个数组,一个存储当前的阶乘值,另一个存储总和。每次循环,先计算当前阶乘(即上一个阶乘乘以当前的i),然后加到总和中。

首先,如何表示大数?可以用一个vector<int>,每个元素代表一位数字,比如低位在前,或者高位在前。一般来说,低位在前处理进位比较方便,因为进位可以push_back到数组后面。比如,数字123用数组表示成[3,2,1],这样乘以某个数的时候,从低位开始乘,进位也自然处理到后面。

比如,计算阶乘的话,初始阶乘是1! = 1,数组是[1]。然后计算2!的时候,乘以2,得到[2]。3!的时候,乘以3,得到[6]。4!的话,乘以4,得到24,数组是[4,2]。以此类推。

然后是加法,总和初始为0,即空数组。每次将阶乘加到总和里。比如第一次加1!,总和变成1。第二次加2!,总和是3。第三次加6,总和是9,等等。

那具体实现的步骤应该是:

1. 初始化总和为0(一个空数组或者0的数组)。

2. 初始化当前阶乘为1(即数组[1])。

3. 对于i从1到n:

a. 将当前阶乘加到总和中。

b. 计算下一个阶乘,即当前阶乘乘以(i+1)。

4. 最后输出总和的数组表示。

不过,需要注意的是,每次的i对应的是i!,所以循环应该从1到n,计算i!,然后加到总和中。例如,当i=1时,阶乘是1!,加到总和;i=2时,阶乘是2!,加到总和,直到i=n。

但是,这样其实每次的阶乘计算应该是累积的。比如,当i=1时,当前阶乘是1!;然后i=2时,当前阶乘是1! *2=2!;i=3时,当前阶乘是2! *3=3!,依此类推。这样可以避免重复计算阶乘,提高效率。

那具体的代码结构应该是:

- 定义两个高精度数,一个存储当前阶乘(初始为1),另一个存储总和(初始为0)。

- 循环从1到n:

- 将当前阶乘加到总和里。

- 更新当前阶乘为当前阶乘乘以(i+1)?或者等一下,这里可能有问题。比如i=1的时候,当前阶乘是1!,加完到总和之后,下一个阶乘应该是2!,也就是当前阶乘乘以2。所以可能循环中的i应该代表当前处理的阶乘i!,然后下一个是i+1的阶乘。

或者,另一种方式:初始当前阶乘是1! = 1。然后,对于每个i从1到n,将当前阶乘加到总和,然后计算下一个阶乘(乘以i+1)?

比如,当i=1时,阶乘是1!,加到总和。然后计算2! =1! *2。然后i=2的时候,加2!到总和,计算3! =2! *3,依此类推。这样循环中的i从1到n,那么正确的做法是:

初始化current_fact = 1! (即1),sum = 0。

然后循环i从1到n:

sum += current_fact

current_fact *= (i+1)

但这样的话,i的取值到n的时候,current_fact是(n+1)!,这不对。因为current_fact应该代表i!,当i从1到n时,每次加的是current_fact,然后current_fact *= (i+1)得到(i+1)!。例如:

i=1时,current_fact是1!,加到sum。然后current_fact *=2,得到2!.

i=2时,加2!到sum,current_fact *=3得到3!.

这样循环结束后,i的值是n,current_fact是n!,但此时已经处理过i=1到n,所以sum是1!+2!+...+n!。这样应该没问题。

或者,另一种思路:current_fact初始为1(1!)。然后循环从1到n:

sum += current_fact

current_fact *= (i+1)

比如,当i=1的时候,sum加上1!,然后current_fact乘以2得到2!。当i=2的时候,sum加上2!,current_fact乘以3得到3!,以此类推,直到i=n的时候,sum加上n!,current_fact乘以(n+1),但此时循环结束。这样总共有n次加法,每次加的是1!, 2!, ...,n!,所以这样是正确的。

那这个思路是正确的。那现在,如何用高精度实现呢?

首先,current_fact和sum都是高精度数,需要用数组来存储每一位。例如,current_fact初始为[1],代表1。sum初始为空或者[0]。

每次加法的时候,sum = sum + current_fact。这需要高精度加法的实现。然后,current_fact = current_fact * (i+1)。这需要高精度乘以低精度的实现。

那现在,如何实现这两个操作?

高精度加法:

函数参数是两个数组,返回它们的和的数组。例如,假设两个数组都是低位在前,那么每一位相加,处理进位。

比如,a = [3,2,1](代表123),b = [5,4](代表45),相加得到[3+5=8, 2+4=6, 1+0=1],进位处理,结果正确吗?这里可能需要重新计算。比如,a是123,b是45,相加是168,即[8,6,1]。那数组的每一位相加,进位应该怎么处理?

正确的加法步骤是:

初始进位为0。对于每一位,sum = a_digit + b_digit + carry。当前位为sum%10,进位是sum/10。循环处理每一位,直到所有位数处理完,并且进位为0。

如果两个数的位数不同,较短的数在后面的位数视为0。

同样,乘法的话,高精度乘以一个整数。比如,数组表示的数和整数k相乘。每一位乘以k,加上进位,处理进位。例如,数组是[3,2,1](123),乘以2,每一位计算:

3*2=6,进位0 → 6

2*2=4+0=4 →4

1*2=2+0=2 →2

结果是[6,4,2] →246。即正确。

如果是进位的情况,比如[5, 8](85)乘以3:

5*3=15 →当前位5,进位1

8*3=24 +1=25 →当前位5,进位2

最后处理进位2 →添加进位到后面。所以结果是5,5,2 →即255。

所以,乘法函数需要处理每一位乘以k,加上进位,并处理进位。

那现在,我需要写出这两个函数:高精度加法、高精度乘低精度。

在代码中,可以用vector<int>来表示高精度数,低位在前。

那么,具体步骤如下:

1. 初始化sum为一个空数组或者0。例如,初始sum是空的,或者[0]。在加法的时候,需要考虑sum的初始值是否正确。

比如,当n=0时,sum是0。但题目中n>=1吗?题目中输入是一个正整数n,所以n>=1。所以sum的初始值可以是0,然后每次循环i从1到n,加current_fact到sum。

所以sum初始化为空或者[0]。然后在第一次加法的时候,sum = sum + current_fact(即0+1=1)。

或者,可以将sum初始化为空,然后在第一次加法时直接等于current_fact。但可能需要处理空的情况。所以可能更简单的是将sum初始化为0,即数组[0]。

那么,初始时current_fact是[1],sum是[0]。然后循环i从1到n:

sum = sum + current_fact

current_fact = current_fact * (i+1)

比如,当i=1的时候,sum变成0+1=1,然后current_fact乘以2得到2。当i=2时,sum加上2得到3,current_fact乘以3得到6。i=3时,sum+6=9,current_fact乘以4得到24。等等。

那现在,如何实现这些操作?

首先,加法函数。函数接收两个vector<int>,返回它们的和,低位在前。

vector<int> add(const vector<int>& a, const vector<int>& b) {

vector<int> res;

int carry = 0;

int max_len = max(a.size(), b.size());

for (int i=0; i < max_len; i++) {

int digit_a = i < a.size() ? a[i] : 0;

int digit_b = i < b.size() ? b[i] : 0;

int sum = digit_a + digit_b + carry;

res.push_back(sum % 10);

carry = sum / 10;

}

if (carry > 0) {

res.push_back(carry);

}

return res;

}

然后,乘法函数,接收一个vector<int>和一个整数k,返回它们的乘积,低位在前。

vector<int> multiply(const vector<int>& num, int k) {

vector<int> res;

int carry = 0;

for (int i=0; i < num.size(); i++) {

int product = num[i] * k + carry;

res.push_back(product % 10);

carry = product / 10;

}

while (carry > 0) {

res.push_back(carry % 10);

carry /= 10;

}

return res;

}

这样,这两个函数应该能处理高精度的加法和乘法。

现在,将这些函数整合到主函数中。

主函数的步骤:

- 输入n。

- 初始化sum为[0],current_fact为[1]。

- 循环i从1到n:

sum = add(sum, current_fact);

current_fact = multiply(current_fact, i+1);

- 输出sum的数组,高位在前。

比如,sum的数组是低位在前,所以输出的时候需要逆序。

例如,sum是[9] →输出9。如果是[8,6,2],则逆序输出268。

测试一下样例输入n=3的情况:

循环i=1到3:

i=1:

sum = add([0], [1]) →[1]

current_fact = multiply([1], 2) →[2]

i=2:

sum = add([1], [2]) →[3]

current_fact = multiply([2],3) →[6]

i=3:

sum = add([3], [6]) →[9]

current_fact = multiply([6],4) →[24]

循环结束,sum是[9],输出9。符合样例。

那这样代码应该正确。

但是,需要考虑n=50的情况,这时候sum会非常大,比如50!本身有几十位,加上其他阶乘的数,所以必须用高精度。

那现在,将这些函数写入C++代码。

可能的代码:

#include <iostream>

#include <vector>

using namespace std;

vector<int> add(const vector<int>& a, const vector<int>& b) {

vector<int> res;

int carry = 0;

int max_len = max(a.size(), b.size());

for (int i = 0; i < max_len; ++i) {

int digit_a = (i < a.size()) ? a[i] : 0;

int digit_b = (i < b.size()) ? b[i] : 0;

int sum = digit_a + digit_b + carry;

res.push_back(sum % 10);

carry = sum / 10;

}

if (carry > 0) {

res.push_back(carry);

}

return res;

}

vector<int> multiply(const vector<int>& num, int k) {

vector<int> res;

int carry = 0;

for (int i = 0; i < num.size(); ++i) {

int product = num[i] * k + carry;

res.push_back(product % 10);

carry = product / 10;

}

while (carry > 0) {

res.push_back(carry % 10);

carry /= 10;

}

return res;

}

int main() {

int n;

cin >> n;

vector<int> sum = {0}; // 初始总和为0

vector<int> current_fact = {1}; // 当前阶乘初始化为1! =1

for (int i = 1; i <= n; ++i) {

// 将当前阶乘加到总和中

sum = add(sum, current_fact);

// 计算下一个阶乘:(i+1)! = current_fact * (i+1)

current_fact = multiply(current_fact, i + 1);

}

// 输出结果,高位在前

for (int i = sum.size() - 1; i >= 0; --i) {

cout << sum[i];

}

cout << endl;

return 0;

}

那这个代码是否能处理n=50的情况?

是的,因为所有计算都是通过高精度进行的,没有溢出问题。但是,当n很大时,比如50,阶乘的位数会很大,但高精度数组可以处理。

测试一下n=3,输出应该是9。其他测试用例,比如n=1,输出1;n=2,输出3;n=4,1+2+6+24=33,输出33。

那这个就是正确的解法了。

AC代码:

#include <iostream>
#include <vector>
using namespace std;

// 高精度加法
vector<int> add(const vector<int>& a, const vector<int>& b) {
    vector<int> res;
    int carry = 0;
    int max_len = max(a.size(), b.size());
    for (int i = 0; i < max_len; ++i) {
        int digit_a = (i < a.size()) ? a[i] : 0;
        int digit_b = (i < b.size()) ? b[i] : 0;
        int sum = digit_a + digit_b + carry;
        res.push_back(sum % 10);
        carry = sum / 10;
    }
    if (carry > 0) res.push_back(carry);
    return res;
}

// 高精度乘法(大数 × 整数)
vector<int> multiply(const vector<int>& num, int k) {
    vector<int> res;
    int carry = 0;
    for (int digit : num) {
        int product = digit * k + carry;
        res.push_back(product % 10);
        carry = product / 10;
    }
    while (carry > 0) {
        res.push_back(carry % 10);
        carry /= 10;
    }
    return res;
}

int main() {
    int n;
    cin >> n;
    
    vector<int> sum = {0};        // 初始化总和为0
    vector<int> current_fact = {1}; // 当前阶乘初始化为1! = 1
    
    for (int i = 1; i <= n; ++i) {
        sum = add(sum, current_fact);  // 累加当前阶乘
        current_fact = multiply(current_fact, i + 1); // 计算下一个阶乘
    }
    
    // 从高位到低位输出结果
    for (int i = sum.size() - 1; i >= 0; --i) {
        cout << sum[i];
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值