洛谷P1009题解(全AC)

一、题目描述

用高精度计算出 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 一名小程序员 三鸽

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值