【P1009】阶乘之和(高精求阶乘之和)

本文介绍了一种使用数组实现的高精度运算方法,并通过实际案例展示了数组大小对程序运行效率的影响。通过优化数组大小,可以显著提升算法的执行速度。


震惊!数组多开大一个10经惨遭TLE!
数组大小为1000010时100跑的慢死,但是为100010时秒杀!
究竟是%!¥#还是@¥#)

题目:

https://www.luogu.org/problem/show?pid=1009


跳过胡扯我们来看一下如何实现【一本正经】:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long LL;
#define RI register int
const int sz = 100010;
inline void read(int &x)
{
    x=0;bool fl=0;
    char c=getchar();
    while(c<'0'||c>'9')
    {if(c=='-') fl=1;c=getchar();}
    while(c>='0'&&c<='9')
    {x=x*10+c-'0';c=getchar();}
    if(fl) x*=-1;
}
inline void write(int x)
{
    if(x<0) putchar('-'),x*=-1;
    if(x/10) write(x/10);
    putchar(x%10+'0');
}
int he[sz],num[sz],s[sz],n;
inline void add()
{
    memset(he,0,sizeof(he));
    he[0]=max(s[0],num[0]);
    for(RI i=1;i<=he[0];++i)
    {
        he[i]+=s[i]+num[i];
        he[i+1]=he[i]/10;
        he[i]%=10;
    }
    while(he[ he[0]+1 ]>0)
    {
        he[ he[0]+2 ]=he[ he[0]+1 ]/10;
        he[ he[0]+1 ]%=10;
        he[0]++;
    }
    s[0]=he[0];
    for(RI i=1;i<=he[0];++i)
        s[i]=he[i];
}
int main()
{
    read(n);
    num[0]=1,num[1]=1;
    s[0]=1,s[0]=0;
    for(RI k=1;k<=n;++k)
    {
        memset(he,0,sizeof(he));
        he[0]=num[0];
        for(RI i=1;i<=num[0];++i)
        {
            he[i]+=num[i]*k;
            he[i+1]=he[i]/10;
            he[i]%=10;
        }
        while(he[ he[0]+1 ]>0)
        {
            he[ he[0]+2 ]=he[ he[0]+1 ]/10;
            he[ he[0]+1 ]%=10;
            he[0]++;
        }
        for(RI i=1;i<=he[0];++i)
            num[i]=he[i];
        num[0]=he[0];
        add();
    }
    for(RI i=s[0];i>=1;--i)
        write(s[i]);
    puts("");
    return 0;
}


s[0]和num[0]表示两个数组的长度
s表示答案,num表示阶乘,先算出阶乘,放在num里,再把s和它相加,更新s
主函数中初始化完毕后是高精乘法,在乘法中完成阶乘运算,用add进行高精加法表示s=s+a
处理好中间的进位后输出

<think>嗯,用户问的是P1009 [NOIP 1998 普及组] 阶乘之和的问题。首先我得回想一下这个题目的具体要。题目应该是要计算S = 1! + 2! + 3! + ... + n!,其中n是一个给定的正整数,对吧?不过可能具体数值范围需要确认,比如n的上限是多少,这会影响到如何处理大数的问题。 然后,用户可能是在准备NOIP或者类似的竞赛题目,所以需要给出算法思路实现方法。因为阶乘增长得非常快,当n比较大的时候,比如n=20,20!已经是一个很大的数,超过普通数据类型的范围,比如64位整型可能都不够用。因此,这道题的关键点应该在于如何处理大数计算,也就是高精度运算。 接下来,我需要考虑如何实现高精度阶乘。通常,高精度计算可以用数组或者字符串来模拟每一位数字,进行逐位的乘法加法操作。比如,用数组的每一位存储数字的一位,低位在前或者高位在前都可能,但通常低位在前更容易处理进位。 首先,阶乘的计算。比如,计算i!的时候,可以用一个数组或列表来保存当前的结果,然后每次乘以i,并处理进位。例如,初始时1!是1,数组是[1]。计算2!的时候,就是1*2=2,数组变成[2]。3!是2*3=6,数组是[6]。到4!的时候,6*4=24,这时候数组可能需要变成[4,2],因为个位是4,十位是2。这样每一位乘以当前的数,然后处理进位。 然后,阶乘之和的问题,需要将每个i!的结果相加。这时候需要另一个数组来保存总,每次将当前的i!加到总数组中。高精度加法需要考虑两个数的位数,逐位相加,进位处理。例如,总数组初始为空,第一次加1!,得到[1]。第二次加2!,即1+2=3,总是3。第三次加6,总是9,依此类推,直到加到n!。 这时候,我需要考虑如何将这两个过程结合起来。比如,对于每个i从1到n,先计算i的阶乘,然后将其加到总里。具体步骤可能如下: 1. 初始化总数组sum为0。 2. 初始化阶乘数组factorial为1(对应1!)。 3. 对于i从1到n: a. 将factorial加到sum中。 b. 计算下一个阶乘,即factorial乘以(i+1),注意i的循环可能需要调整,比如如果是i从1到n,那么下一个阶乘是乘以i+1?或者初始是1!,然后i从2到n,乘以i。这点需要仔细考虑循环结构。 4. 最后输出sum数组的值。 不过具体实现时,阶乘的计算应该在每次循环中乘以当前的i。例如,初始时factorial是1(1!)。当i=1时,加到sum里,然后计算i=2的时候,factorial *=2得到2!,再加到sum里。这样循环到i=n。 因此,正确的流程应该是: - sum初始化为0。 - factorial初始化为1。 - 对于i从1到n: - sum += factorial - factorial *= (i+1) ? 这里可能有问题,因为当i=1的时候,下一个阶乘是2! =1! *2,所以应该是factorial *=i+1?或者更准确地说,当i从1到n时,每次循环处理i的阶乘,加到sum之后,计算下一个阶乘应该是乘以i+1。或者更简单的是,在循环内部,先加到sum,然后更新factorial为factorial * (i+1)? 或者,另一种方式是,factorial初始化为1(即1!),然后循环从i=1到n,每次将factorial加到sum,然后计算下一个阶乘,即factorial *= (i+1)。例如: i=1时,factorial是1!,加到sum。然后计算factorial *=2得到2!,i=2时加到sum,接着factorial *=3得到3!,直到i=n。 这样循环的次数是n次,每次处理i!。例如,当n=3时,i=1处理1!,i=2处理2!,i=3处理3!。这似乎没问题。不过这里i的循环可能应该从1到n,而阶乘的更新是乘以(i+1)吗?或者应该循环中的i对应的是当前的阶乘数? 或者,另一种思路是,循环i从1到n,每次计算i的阶乘,然后加到sum。这时候,计算i!可以通过从1乘到i,但是这样每次都要重新计算阶乘,效率不高。所以更高效的做法是,利用之前的阶乘结果,乘以当前的i得到新的阶乘。例如: 初始factorial为1(即1!) sum += factorial (i=1) 然后i=2时,factorial *=2 →2!,sum +=2! i=3时,factorial *=3 →6=3!,sum +=3!,依此类推。 这样,循环变量i从1到n,每次循环时: sum += factorial 然后 factorial *= (i+1) ? 或者,这样是否正确呢?例如,当i=1时,计算的是1!,然后下一个阶乘是乘以2得到2!。所以,应该在循环中,i从1到n,每次处理i的阶乘,而阶乘的计算是通过前一个阶乘乘以当前的i? 或者,可能我之前的方法有问题。正确的应该是,初始factorial为1(1!),sum加上之后,然后i从2到n,每次factorial *=i,sum加上factorial。例如,当n=3时: 初始化sum=0,factorial=1。 i=1: sum += factorial → sum=1 然后,因为接下来要计算2!,需要将factorial乘以2 → factorial=2 i=2: sum +=2 → sum=3 factorial *=3 →6 i=3: sum +=6 → sum=9 循环结束。所以,这样处理的话,循环次数是n次,i从1到n,每次处理i!,然后乘以i+1?这似乎有问题,因为当i=1时,下一个是i+1=2,所以乘以2得到2!。然后i=2的时候,乘以3得到3!,此时i的循环次数应该是n次,对吗?这可能循环变量的设计有关。 或者,可能更好的方式是,循环变量i从1到n,每次循环中,factorial *=i,这样初始的factorial为1,当i=1时,factorial *=1 →1!,加到sum。然后i=2,factorial *=2 →2!,加到sum。这样循环i从1到n,每次循环中,factorial *=i,然后加到sum。但这样的话,初始的factorial应该是1,然后在循环中首先乘以i,得到i!。例如: sum =0 factorial=1 for i in 1..n: factorial *=i # 此时factorial是i! sum +=factorial 这样是否正确?比如,当i=1时,factorial=1*1=1!,加到sum。i=2时,factorial=1*1*2=2!,加到sum。i=3时,factorial=1*1*2*3=3!,依此类推。是的,这样是正确的。但这样每次循环中,factorial *=i,初始为1,这样得到的是i!。那这样的话,i从1到n循环,就能得到1!到n!的。这可能更直接。 所以,问题的核心在于如何高效计算阶乘并累加,同时处理大数问题。因为n可能较大,比如题目中的测试点可能有n=50或者更大的情况,这时候必须使用高精度计算。 那现在,如何用高精度实现阶乘加法? 首先,高精度阶乘计算:可以用一个数组或列表,每一位存储一个数字,比如低位在前,这样进位处理方便。例如,数字1234可以存储为[4,3,2,1],这样当乘以一个数时,可以逐位相乘并处理进位。 例如,计算3!: 1. 初始为1 → [1] 2. 乘以2 → [2] 3. 乘以3 → [6] 所以3!是6,存储为[6] 计算4!: 6乘以4 → 每位处理:6*4=24 → 4进位2 → 所以结果是[4,2] → 即24,但存储是低位在前,所以表示为[4,2],即24。 然后,计算阶乘之和的时候,需要将每个阶乘的结果相加。比如sum初始为空,或为0,然后依次加上每个阶乘高精度数。 高精度加法需要处理两个数的位数,以及进位。例如,当前sum是[3](即3),要加上[6](即6),得到[9]。当sum是[9](9),加上[24](存储为[4,2]),那么需要将每一位相加,0+4=4,9+2=11,所以结果是[4,1],进位1,所以最终变为[4,1,1],即 114? 不对,可能哪里弄错了。 或者,可能我的存储顺序有问题。例如,sum是[9](即9),而加上[4,2](即24),则sum的每一位是9在个位,而另一个数是4在个位,2在十位。相加的话,个位9+4=13,记录3,进位1。十位0+2+1=3。所以结果是[3,3,1]吗?或者存储顺序的问题? 或者,正确的加法方式应该是这样的:例如,sum是[9],即数字9,另一个数是[4,2],即24。加法时,个位相加9+4=13,所以个位是3,进位1。十位0+2+1=3,所以十位是3。所以结果是[3,3],即33,也就是9+24=33。存储顺序是低位在前,所以33的表示是[3,3]。 这样,加法的时候,需要将sum当前阶乘的数组进行逐位相加,并处理进位。 那具体的实现步骤可能如下: 1. 初始化sum数组为空,或者初始为0(即[0])。 2. 初始化factorial数组为[1]。 3. 对于i从1到n: a. 将factorial数组加到sum数组中,使用高精度加法。 b. 计算factorial数组乘以(i+1)得到下一个阶乘?或者,步骤a之后,计算factorial *= (i+1)?或者,这里的i的处理方式可能有误,需要重新考虑。 或者,根据之前的分析,正确的阶乘计算方式是在每次循环中,factorial *=i。例如: 初始化factorial为[1] sum初始化为[0] 然后,循环i从1到n: factorial = factorial * i → 这样,第一次i=1时,factorial是1*1=1,即1!。加到sum,sum变为1。第二次i=2,factorial是1*2=2 →2!,加到sum得到3。第三次i=3,factorial是2*3=6→3!,sum变成9,依此类推。 哦,原来如此。这样,每次循环中,i的当前值被乘到factorial中,得到i!。例如: i=1时,factorial *=1 →1!,sum +=1! i=2时,factorial *=2 →2!,sum +=2! i=3时,factorial *=3 →3!,sum +=3! 这样,循环n次,i从1到n,每次得到i!,然后加到sum中。这样是正确的。 那现在,如何实现高精度乘法加法? 首先,高精度乘法:一个高精度数(数组)乘以一个整数i。例如,数组是[4,2](表示24),乘以i=5,得到120。具体步骤是: 每一位数字乘以i,并处理进位。例如: 初始数组是[4,2],i=5。 个位4*5=20,保留0,进位2。 十位2*5=10,加上进位2得到12,保留2,进位1。 然后,进位1需要处理,所以结果数组变成[0,2,1],即0在个位,2在十位,1在百位,即120。 存储顺序是低位在前,所以数组是[0,2,1]。 所以,高精度乘法的步骤是: 1. 初始化进位为0。 2. 遍历高精度数的每一位: a. 当前位乘以i,加上进位。 b. 新的当前位的值为总对10取余。 c. 新的进位为总对10取整。 3. 处理剩余的进位,如果大于0,需要添加到高位。 然后,高精度加法:将两个高精度数相加,例如sum数组factorial数组相加。假设sum是[3](3),factorial是[0,2,1](120),那么相加结果是123,存储为[3,2,1]。 具体步骤是: 1. 初始化进位为0。 2. 遍历两个数组的每一位,直到处理完较长的数组。 3. 每一位的等于sum的当前位(如果存在)加上factorial的当前位(如果存在)加上进位。 4. 新的当前位为%10,进位为//10。 5. 最后处理剩下的进位。 所以,现在的问题是,如何用代码实现这些步骤,或者给用户提供算法的步骤,让他们自己编写代码。 对于用户的问题,他们可能需要一个算法思路,以及如何处理高精度运算。此外,可能需要给出一个代码示例,比如用C++或Python实现,但用户可能更倾向于Python,因为处理大数相对容易,但题目可能要用C++。不过原题是NOIP普及组的题目,所以可能需要用Pascal或者C++。不过用户可能只是想了解思路。 所以,在回答中,应该分步骤说明: 1. 题目分析:计算阶乘之和,但n较大时,数值超过普通数据类型,需用高精度。 2. 算法思路: a. 高精度计算阶乘:用数组存储每一位,每次乘以i,处理进位。 b. 高精度加法:将每个阶乘结果累加到总中。 3. 具体步骤: a. 初始化阶乘数组数组。 b. 循环i从1到n: i. 计算当前阶乘(乘以i)。 ii. 将当前阶乘加到总中。 c. 输出总数组。 4. 实现细节: a. 高精度乘法的实现。 b. 高精度加法的实现。 c. 数组的存储方式(低位在前)。 可能的陷阱包括: - 阶乘乘法的顺序,例如,在循环中必须正确更新阶乘数组。 - 处理进位的时候,特别是乘法加法中的进位可能不止一位。 - 数组的索引管理,低位在前的高精度处理。 例如,当n=3时,总应该是1!+2!+3!=1+2+6=9。当n=10时,总是4037913,但具体需要验证。 现在,考虑如何用Python实现高精度,虽然Python本身支持大整数,但为了教学目的,可能需要手动模拟这个过程,以展示高精度处理的步骤。 或者,用户可能需要知道如何处理,而不管具体语言。所以,给出步骤即可。 比如,对于阶乘计算: 初始化fact为[1](表示1)。 sum为[0]。 然后,对于每个i从1到n: fact = fact * i → 高精度乘法。 sum = sum + fact → 高精度加法。 然后,输出sum的数组,按逆序输出。 例如,n=3时: i=1: fact = [1] *1 → [1] sum = [0] + [1] → [1] i=2: fact = [1] *2 → 2 → [2] sum = [1] + [2] →3 → [3] i=3: fact = [2] *3 →6 → [6] sum = [3] + [6] →9 → [9] 所以,结果数组是[9],输出9。 当n=10时,正确的结果是4037913,那么高精度计算会得到这个结果。 在代码实现中,可以用数组来存储这些数字。例如,Python中可以用列表,每个元素是数字的一位,低位在前。 然后,实现乘法函数加法函数。 例如,乘法函数: def multiply(num, i): carry =0 result = [] for digit in num: product = digit * i + carry result.append(product %10) carry = product //10 while carry >0: result.append(carry%10) carry = carry //10 return result 加法函数: def add(a, b): result = [] carry =0 max_len = max(len(a), len(b)) for i in range(max_len): digit_a = a[i] if i < len(a) else 0 digit_b = b[i] if i < len(b) else 0 total = digit_a + digit_b + carry result.append(total%10) carry = total //10 if carry >0: result.append(carry) return result 然后,主程序: n = int(input()) fact = [1] sum_total = [0] for i in range(1, n+1): # 计算当前阶乘:fact = fact * i fact = multiply(fact, i) # 将阶乘加到总 sum_total = add(sum_total, fact) # 输出总,逆序输出 print(''.join(map(str, reversed(sum_total)))) 这样,应该可以得到正确的结果。例如,n=3,输出9;n=10,输出4037913。 但需要测试一下代码是否有错误。例如,乘法是否正确,加法是否正确。 比如,测试阶乘计算: i=1: fact初始为[1] → multiply([1],1) → [1] i=2: multiply([1],2) → [2] i=3: multiply([2],3) → 6 → [6] i=4: multiply([6],4) → 24 → [4,2] 是的,正确。 加法测试: sum初始为[0],第一次加[1] → [1] 第二次加[2] → [3] 第三次加[6] → [9] 正确。 当i=4时,fact是[4,2](24),sum是9 →9+24=33 → [3,3] 所以,sum_total在四次循环后是1+2+6+24=33,即当n=4时,输出33。 这正确,因为1!+2!+3!+4! =1+2+6+24=33. 所以,代码应该正确。 综上,用户的问题需要介绍这个题目的解法,重点在于高精度处理阶乘。因此,回答的结构应该包括题目分析、算法思路、具体实现步骤,以及可能的代码示例(如果需要的话)。同时,根据用户的要,使用中文回答,结构清晰,步骤详细。</think>### P1009 [NOIP 1998 普及组] 阶乘之和 解答 #### 题目分析 要计算 $S = 1! + 2! + 3! + \dots + n!$($n \leq 50$)。 **核心难点**:阶乘增长极快(如 $20! \approx 2.43 \times 10^{18}$),远超普通整数范围,需用**高精度运算**处理。 --- #### 算法思路 1. **高精度乘法**:逐位计算阶乘,用数组存储每一位(低位在前,便于处理进位)。 - 示例:计算 $4!$ $$1 \xrightarrow{\times 2} 2 \xrightarrow{\times 3} 6 \xrightarrow{\times 4} 24 \quad \text{(存储为 } [4,2]\text{)}$$ 2. **高精度加法**:将每个阶乘结果累加到总中。 - 示例:$9 + 24 = 33$ $$[9] + [4,2] \rightarrow [3,3] \quad \text{(即 } 33\text{)}$$ --- #### 具体步骤 1. **初始化**: - 阶乘数组 `fact = [1]`(表示 $1!$) - 总数组 `sum_total = [0]` 2. **循环计算**($i$ 从 $1$ 到 $n$): - **计算当前阶乘**:`fact = fact * i`(高精度乘法) - **累加到总**:`sum_total = sum_total + fact`(高精度加法) 3. **输出结果**:将 `sum_total` 逆序输出(高位在前)。 --- #### 代码实现(Python 模拟高精度) ```python def multiply(num, i): carry = 0 result = [] for digit in num: product = digit * i + carry result.append(product % 10) carry = product // 10 while carry > 0: result.append(carry % 10) carry = carry // 10 return result def add(a, b): result = [] carry = 0 max_len = max(len(a), len(b)) for i in range(max_len): digit_a = a[i] if i < len(a) else 0 digit_b = b[i] if i < len(b) else 0 total = digit_a + digit_b + carry result.append(total % 10) carry = total // 10 if carry > 0: result.append(carry) return result n = int(input()) fact = [1] sum_total = [0] for i in range(1, n + 1): fact = multiply(fact, i) sum_total = add(sum_total, fact) print(''.join(map(str, reversed(sum_total)))) ``` --- #### 示例验证 - **输入**:`n = 3` **输出**:`9`($1! + 2! + 3! = 1 + 2 + 6 = 9$) - **输入**:`n = 10` **输出**:`4037913` --- #### 关键点总结 1. **高精度乘法**:逐位计算并处理进位。 2. **高精度加法**:对齐位数后逐位相加。 3. **存储顺序**:低位在前简化进位逻辑。 通过分步模拟高精度运算,可有效解决大数阶乘问题。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值