本人也是第一次在csdn上面写博客,如果有什么解释不清和逻辑上的错误,还请大家能够海涵并能不灵赐教。
这道题来自于洛谷的第三个提单,用来实现从一的阶乘加到n的阶乘的和。通常情况下,我们会直接通过for循环来遍历每一个阶乘并相加,比如说下面这样:
#include<stdio.h>
int main()
{
int n,i,j;
unsigned long long sum=0,num=1;
scanf ("%d",&n);
for (i=1;i<=n;i++)
{
for (j=1;j<=i;j++)
{
num=num*j;
}
sum=sum+num;
num=1;
}
printf ("%lld",sum);
}
这样看上去简洁明了,很好理解,但存在一个很大的问题,那就是如果给出的数字太大,则会出现数据溢出。这道题用到了无符号长长整型,无符号长长整形占据八个字节,其大小约为2的64次方,范围为0到1844 6744 07337 0955 1615之间,为c语言能储存的最大数字。但是当给n赋值为50的时候,真实的结果应该是31035053229546199656252032972759319953190362094566672920420940313
本以为unsigned long long int已经很大了,但是和上面这个数字比起来,简直不值一提。全宇宙的原子个数约为10的80次方。面对如此庞大的数字,我们已经不能使用变量来存储,只能通过数组来处理。但应该如何处理和实现这个代码呢,我们先看下面这个例子;
3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 |
* | 2 | ||||||
6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 |
通过这样,我们用一个数组的每一位来和要成的数字进行乘法运算,就可以得到结果,其思想和小学乘法的思想一致。
但是出现了第一个问题,如果某一个的数字超过了十,那么该如何处理呢,思路如下:
1 | 2 | 3 | 2 | 3 | 1 | 2 | 9 |
* | 3 | ||||||
3 | 6 | 9 | 6 | 9 | 3 | 6 | 27=20+7 |
3 | 6 | 9 | 6 | 9 | 3 | 6+2 | 7 |
就像这样,将个位的数字取十的模,再将其除以十,将十位以上的数字加到前一位上,这样就可以将每一位数字都化为一位数字。(在这里要提前说明要考虑数组长度不够的情况)
之后的第二个问题,数组中的数字是从前往后排列的,但是加,减,乘都是从各位开始的,如果两个数字长度不齐,就会在计算中出现麻烦。为了解决这个问题,我们不妨逆天道而行,数学中从前往后进位,我们可以从后往前进位。
第三个问题,构建好数组后该如何输出呢,这个问题其实不难解决,我们的数字是逆向储存的,我们也需要找到起始位点,最后从后往前输出,即找到从后往前的数字中,不为零的那个数。
综上,我们就有了个大概得框架,这样就有了实现代码的理论基础。
代码如下:
#include<stdio.h>
int main()
{
int i,num[151]={0},arr[151]={0},n,j;//num用来计算所有阶乘的和,arr用来计算所需的阶乘结果,数组后的数字应根据实际情况调整大小
scanf("%d", &n);
num[0]=arr[0]=1;
for (i=2;i<=n;i++){
for (j=0;j<150;j++)//这里运算的结果不能超过100位,如果超出,就需要扩大该值(运算结果的大概位数)
arr[j]*=i;//构建阶乘
for (j=0;j<150;j++)
if (arr[j]>9){
arr[j+1] += arr[j]/10;//倒序进位
arr[j]%=10;
}
for (j=0;j<150;j++){
num[j]+=arr[j];//计算结果
if (num[j]>9) {
num[j+1] += num[j]/10;//倒序进位
num[j]%=10;
}
}
}
for (i=150;i>=0;i--){
if (num[i]!=0) break;//找到起始位
}
for (j=i;j>=0;j--) printf("%d", num[j]);//倒序输出
return 0;
}
具体的细节已经在上面的代码中注释。
感谢大家能够耐心听我的讲解。