一、题目描述
用高精度计算出 S=1!+2!+3!+⋯+n!S=1!+2!+3!+⋯+n!(n≤50n≤50)。
其中
!
表示阶乘,定义为 n!=n×(n−1)×(n−2)×⋯×1n!=n×(n−1)×(n−2)×⋯×1。例如,5!=5×4×3×2×1=1205!=5×4×3×2×1=120。
二、思路分析
读完题目我们了解到,计算的模块分为累加和累乘两部分。这题需要注意的是以下这句话:
【数据范围】
对于 100% 的数据,1≤n≤50。
而int的取值范围为: -2^31——2^31-1,即-2147483648——2147483647,约2*10E9。
而50的阶乘远远超过这个范围...
50!=31035053229546199656252032972759319953190362094566672920420940313
即3*10E64
所以哪怕我们舍弃了int,使用unsign int也是不够的。那么我们该怎样才能最大程度地省下内存呢?这种情况我们就需要用到高精度的写法——
用字符串对每个数位的数字进行记录,再把它们拼接起来。
[?]但是内存是怎么省下来的呢?
int a = 30000; //创建30000个字节空间!
int b[6] = {3,0,0,0,0}; //创建6个字节空间~
//tips:多开一个空间是为了接收'\0'
高精真是很好的算法呢~
我们先用常规算法来一遍累加累乘。
2.1 累乘部分
n! = 1*2*3*...*n
计算这个阶乘并不困难。我们只需要for循环、1个存储量和1个变量就够了。
int n,jc=1; //jc记得要初始化!!
scanf("%d",&n);
for(int i=2;i<=n;i++){ //可以从2开始乘哦,记得别漏了最后的n
jc *= i;
}
2.2 累加部分
S(n) = 1! + 2! + ... + n!
最容易想到的是,创建数组jc[n],jc[0] = 1! , jc[1] = 2! ...
但这样意味着阶乘需要多次从1开始计算,太浪费内存了。
所以我们每升一次阶就加到总和sum里面。即:
int sum = 0;
for(int i=1;i<=n;i++){
jc *= (i+1); //由i!升阶变成(i+1)!
...
//得到jc = (i+1)!
sum += jc;
}
累加和累乘的部分就这样完成啦,so easy。但这都是使用int型的数据,因为当阶乘阶数过高时数据会溢出,所以拿不到全AC。
现在我们需要的就是——高精度算法。
前面我们讲了高精度是把每位的数字分别存到数组中,因此高精度的读取和加减乘除方式也会和常规有所不同。
2.3 高精度
2.3.1 高精度-读取
高精度的读取遵循从低位到高位的原则。先来串代码,再进行讲解
int i;
int a[101] //最多可容纳100数位
int aL=0;
//输入mv
while(i>0){
a[aL++] = i%10;
//等价于
//{
// a(aL) = i%10;
// aL++;
//}
i /= 10;
}
我们需要三个变量,分别用来最初输入(i)、记录数位总长(aL)和存储大数(a)。
值得注意的是,输入数中较高的数位数字会存储到数组中靠后的位置。
举个例子,如果存入三位数,那么百位的数字会存储在a[2],而个位在a[0]。
因此最后我们输出大数的时候,也要从数组的末项向首项输出。
2.3.2 高精度-加法
高精度的加法和我们小学学过的竖式计算很相像。
个位+个位,十位+十位………超过10就进位~ awa
假如我们需要两个三位数相加,这样写就可以啦。
for(int ai=0;ai<aL;ai++){
//ai和bi分别是a数组与b数组内部的指针
//(不是那个带*的哈,这里只是比喻)
for(int bi=0;bi<bL;bi++){
i = max(ai,bi); //避免a数的数位位少于b数的数位,造成加错位置的问题
c[i] = a[i]+b[i];
}
}
2.3.3 高精度-进位
大体思路是把本位数字除以10,余数加给下一位数字,商留在本位。
写成程序就是这样的:
//reg的进位和长度
for(int i=0;i<regL;i++){
if(reg[i]>9){ //大于9就进位
reg[i+1] += reg[i]/10; //商加到高位
reg[i] %= 10; //余数留在本位
}
}
2.3.4 高精度-乘法
三位数*三位数,极限长度是六位数->n位*n位*<2n位。
所以我们写成:
for(int a=0;j<aL;a++){
for(int b=0;h<=bL;b++){
reg[bi+ai] += b[bi]*a[ai];
}
}
2.4 清空储存器
这里我们需要用到memset()函数,用来将指定大小的内存块设定为指定值。
//清空升阶器
memset(reg,0,sizeof(reg)); //将指定大小的内存块设定为指定值
上图的代码用于将reg数组起的sizeof(reg)个字符的位置的值全设置为0
最后用for循环传值给总和即可~
下图为整体代码:
#include <bits/stdc++.h>
using namespace std;
//定义参数
int n;
//四个机器:升阶器、阶乘、寄存器、总存器
int lv[100],st[100],reg[100],tot[100];
//四个机器的长度
int lvL,stL=1,regL=1,totL;
//极限长度
int exL=1;
int main(){
st[0] = 1; //初始化
cin >> n;
//求S(n). S(n)=1!+2!+...+n!
for(int i=1;i<=n;i++){
lvL = 0; //刷新lvL
int mv = i;
//输入i
while(mv>0){ //覆盖旧lv(刷新)
lv[lvL++] = mv%10;
mv /= 10;
}
//(i-1)! * i = i!
for(int j=0;j<lvL;j++){
for(int h=0;h<=stL;h++){ //[?]h<stL
reg[h+j] += st[h]*lv[j];
}
}
//reg的进位和长度
for(int i=0;i<regL;i++){
if(reg[i]>9){
reg[i+1] += reg[i]/10;
reg[i] %= 10;
}
}
//reg最高位刷新(n<=50,不可能连进两位)
if(reg[regL]){
regL++;
}
//本次阶乘值转移到寄存器
totL = stL; //记录stL,以便计算总和应取的长度
exL = max(exL,regL);
for(int i=0;i<regL;i++){
st[i] = reg[i];
}
stL = regL;
regL = totL+lvL;
//清空升阶器
memset(reg,0,sizeof(reg)); //将指定大小的内存块设定为指定值
//传值给总和
for(int i=0;i<exL;i++){
tot[i] += st[i];
if(tot[i]>9){
tot[i+1] += tot[i]/10;
tot[i]%=10;
}
}
}
while(tot[exL]==0 && exL>0) exL--;
for(int i=exL;i>=0;i--){
cout << tot[i];
}
return 0;
}
感谢阅读~Thanks♪(・ω・)ノ
from 一名小程序员 三鸽