高精度问题

在写题的过程中我们还是会经常遇到高精度这类问题,而在面对这一问题时,没有接触和了解这个算法的同学,可能会比较懵,不知如何下手,这篇文章就较为具体的为大家介绍一下高精度问题。

1.简介

高精度问题通常指的是处理超出标准数据类型(如intlong等)所能表示的范围的数值计算问题。由于计算机中的整数类型有固定的位数和范围限制,当需要进行大数运算或者高精度计算时,标准数据类型高精度问题常见于以下场景:

大数加法:多个大数相加时,结果也可能超出标准整数类型的最大值。

大数乘法:当两个大数相乘时,结果可能超出intlong的最大值。

大数阶乘:在某些算法中,可能需要进行大数除法,以得到精确的商和余数。

大数幂运算:计算一个数的幂次方,结果可能是一个非常大的数。

2.基本思路

这里我以加法,乘法这种双变量的运算法则来说明。

首先我们先建立两个字符串数组,并且将它们输入,再建立两个int数组,我们需要估计这两个数进行运算后的大致长度,以保证在后面的运算不会出现数组越界的情况同时也不会浪费空间,就好比a+b,两个数相加后最大的长度也只可能是两个中最大的一个的长度 + 1,所以就是L =lmax+1。

接下来使用for循环将两个字符串的每一项进行 - '0',将该数据的每一项成功以整形记录进数组中。

紧接着就是最主要的部分了,对两个数组进行运算,在运算之前,大家得回想小时候学习四则运算法则时,自己是怎么进行计算的,这里我们就要使用相同的办法。以加分为例,我们从第一项开始计算,a[1]+b[1],然后进行判断是否大于10,如果大于则a[2]+1,a[1]%=10,然后依次递推,算出每一位为止。

最后就是输出啦,输出的时候需要从L开始往前面输出,因为我们是从个位开始计算的,先输出的是较大的位数。

我们现在说的是个基本框架,就是提供高精度算法的基础思路,在解决不同题目的时候要随机应变,高精度可能是在计算过程中才运用上,不像上面说的简简单单,直接让你输入两个超级大的数据,然后就进行运算。而且有时候需要你自己去发现这题目是需要高精度的,即可能是在算斐波那契数时,或者有涉及阶乘,幂函数这种,就需要注意一下数据范围,估计是否会超long long。这里要注意阶乘这个的递增是非常非常快的,比起10的幂次方快了很多倍(当然是指阶数较大的情况,在n<=10的时候,10的幂次方还是比阶乘快的)。

接下来就是各种运算的代码啦,对每个代码我们还是会再进行解释。

3.高精度加法

洛谷 P1601 A+B Problem(高精)

题目描述

高精度加法,相当于 a+b problem,不用考虑负数

输入格式

分两行输入。a,b≤10500。

输出格式

输出只有一行,代表 a+b 的值。

输入输出样例

输入 #1 

1
1

 输出 #1

2

输入 #2

1001
9099

输出 #2 

10100
 说明/提示

20% 的测试数据,0≤a,b≤10^9;

40% 的测试数据,0≤a,b≤10^18。

AC代码
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
int main()
{
	string s1, s2;
	cin >> s1 >> s2;
	int len1 = s1.size();
	int len2 = s2.size();
	int l = max(len1, len2) + 1;
	vector<int> a(l, 0);
	vector<int> b(l, 0);
	for (int i = 0; i < len1; i++) {
		a[i] = s1[len1 - 1 - i] - '0';
	}
	for (int i = 0; i < len2; i++) {
		b[i] = s2[len2 - 1 - i] - '0';
	}
	vector<int> c(l,0);
	for (int i = 0; i < l; i++) {
		c[i] += a[i] + b[i];//注意此处是 += 不是 = 。
		if (c[i] > 9) {
			c[i + 1] += 1;
			c[i] %= 10;
		}
	}
	for (int i = l - 1; c[i] == 0 && i > 0; i--) {//注意结束条件
			l--;
	}
	for (int i = l - 1; i >= 0; i--) {
		cout << c[i];
	}
	return 0;
}

首先我们创建两个string类,记录输入的数据,然后由于要创建vector数组(之后会将STL中常见的容器),我们要先求出总长度L,为何不直接以len1和len2来创建呢?有时间的同学可以去试一试,在运行的啥时候你就会发现报错了,运行不了,只是因为当两个数的最大位相加后会进一的情况,L就大于len1和len2,而在后面相加的for循环是以i<L为结束条件的,当运行到i = L - 1时,数组就发生越界了,所以才会报错。

接着就是将char型转化为int型,这里要注意的是,不是直接a[i] = s[i] - '0',因为数据是按大到小存入的,先是千位,在是百位,十位,个位。而我们计算时是从个位开始向高位加的,所以我们是a[i] = s[len - 1 -  i] - '0' 来实现的,当然也可以a[len - 1 - i] = s[i] - '0' , 同个道理。

然后就是运算咯,for循环从i = 0到i = l - 1,将每一位相加,如果a[i] > 9 就进位。

其次是减去数组后面的0, 如果两个数相加后最高位没有进一,那么len - 1的位置就是0了,我们就要从末尾开始判断是否为0,是则减一,这里要注意的是需要加上 i > 0 ,因为如果是 0 + 0 = 0的话,不加此条件,l 会被全部减掉,直到为0,那这样我们就无法输出了。 

最后就是输出,从最高位开始,即 l - 1开始,知道i = 0结束。

这样我们就顺利解决高精度加法的问题啦。

4.高精度减法

减法的话和加分没什么区别,就是加的变成减的,然后在长度的确定上变成比较len1和len2谁大,大的即是lmax。然后在运算的时候在a-b不足时需要向向前一位借一。

#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
int main()
{
	string s1, s2;
	cin >> s1 >> s2;
	int len1 = s1.size();
	int len2 = s2.size();
	int l = max(len1, len2);
	vector<int> a(l, 0);
	vector<int> b(l, 0);
	for (int i = 0; i < len1; i++) {
		a[i] = s1[len1 - 1 - i] - '0';
	}
	for (int i = 0; i < len2; i++) {
		b[i] = s2[len2 - 1 - i] - '0';
	}
	vector<int> c(l, 0);
	for (int i = 0; i < l; i++) {
		c[i] += a[i] - b[i];
		if (c[i] < 0) {
			c[i + 1] -= 1;
			c[i] += 10;
		}
	}
	for (int i = l - 1; c[i] == 0 && i > 0; i--) {
		l--;
	}
	for (int i = l - 1; i >= 0; i--) {
		cout << c[i];
	}
	return 0;
}

这里可以再加个判断a是否会小于b,会的话就加个plog记录,达到为负数的条件就变成b-a然后在输出时加上个“-“就行。

5.高精度乘法

乘法当然也没什么特别大的区别,无非就是在长度L和相乘时有些不同,长度L在确定的时候是 L = len1 + len2,这个举个例子就可以想出来了,99 + 99 = 9801,99都是两位数的最大值,len1 = 2,len2 = 2,L = 4,所以L的最大长度只能是len1 + len2。

运算相乘的话,就有点不同,想想你平时是怎么算两数相乘的,下乘上,下面一位一位的乘以上面的整个数,然后个位乘完的值最末端在个位,十位乘完的值最末端在十位,以此类推,那么这样我们就写两个for循环,外层循环就是下面的数,内层循环就是上面的数,然后c[i+j] = a[i] * b[j]。

P1303 A*B Problem

题目背景

高精度乘法模板题。

题目描述

给出两个非负整数,求它们的乘积。

输入格式

输入共两行,每行一个非负整数。

输出格式

输出一个非负整数表示乘积。

输入输出样例

输入 #1

1 
2

输出 #1

2
说明/提示

每个非负整数不超过 10^2000。

AC代码
#include<iostream>
#include<cstring>
#include<vector>
using namespace std;
int main()
{
	string s1, s2;
	cin >> s1 >> s2;
	int len1 = s1.size();
	int len2 = s2.size();
	int l = len1 + len2;
	vector<int> a(l, 0);
	vector<int> b(l, 0);
	for (int i = 0; i < len1; i++) {
		a[i] = s1[len1 - 1 - i] - '0';
	}
	for (int i = 0; i < len2; i++) {
		b[i] = s2[len2 - 1 - i] - '0';
	}
	vector<int> c(l, 0);
	for (int i = 0; i < len1; i++) {
		for (int j = 0; j < len2; j++) {
			c[j + i] += (a[i] * b[j]);
		}
	}
	for (int i = 0; i < l; i++) {
		if (c[i] > 9) {
			c[i + 1] += c[i] / 10;
			c[i] %= 10;
		}
	}
	for (int i = l - 1; c[i] == 0 && i > 0; i--) {//注意结束条件
		l--;
	}
	for (int i = l - 1; i >= 0; i--) {
		cout << c[i];
	}
	return 0;
}

6.高精度阶乘

阶乘的话,可以分为求单个数的阶乘比如4!,也可以是阶乘之和。阶乘的话就不需要两个数组来记录了,一个即可,不过长度的话就需要我们自己定义了,一般得定得比较大,因为阶乘的增长是非常快的。

然后就是运算部分,我们要实现1*2*3*……*n,就得使用for循环将i循环到n,然后因为我们是以数组记录需要再加一个循环将i乘以数组每一位,然后记得每一次乘以 i 后用for循环将每一位进行进位。

其他的话就没有什么变化啦,相比之下,阶乘的代码更加简洁。

指定数的阶乘

#include<iostream>
#include<vector>
using namespace std;
int main()
{
	int n;
	int len = 100;
	cin >> n;
	vector<int> a(len, 0);
	a[0] = 1;
	for (int i = 2; i <= n; i++) {
		for (int j = 0; j < len; j++) {
			a[j] *= i;
		}
		for (int j = 0; j < len; j++) {
			if (a[j] > 9) {
				a[j + 1] += a[j] / 10;
				a[j] %= 10;
			}
		}
	}
	for (int i = len - 1; i > 0 && a[i] == 0; i--) {
		len--;
	}
	for (int i = len - 1; i >= 0; i--) {
		cout << a[i];
	}
	return 0;
}

接下来就是阶乘之和了。如果懂了单个阶乘是怎么求了的话,阶乘和也很容易了,不过是再加一个数组,然后再a[j] *= i ,后面再把a[j]加到b[j]上面,这样就可以实现阶乘相加了,当然后面b的每一项也需要 %=10 。然后输出就变成输出b就完事咯。 

P1009 [NOIP1998 普及组] 阶乘之和

题目描述

用高精度计算出 S=1!+2!+3!+⋯+n!(n≤50)。

其中 ! 表示阶乘,定义为 n!=n×(n−1)×(n−2)×⋯×1。例如,5!=5×4×3×2×1=120。

输入格式

一个正整数 n。

输出格式

一个正整数 S,表示计算结果。

输入输出样例

输入 #1

3

输出 #1

9
说明/提示

【数据范围】

对于 100% 的数据,1≤n≤50。

AC代码
#include<iostream>
#include<vector>
using namespace std;
int main()
{
	int n;
	int len = 100;
	cin >> n;
	vector<int> a(len, 0);
	vector<int> b(len, 0);
	a[0] = 1;
	b[0] = 1;
	for (int i = 2; i <= n; i++) {
		for (int j = 0; j < len; j++) {
			a[j] *= i;
			b[j] += a[j];

		}
		for (int j = 0; j < len; j++) {
			if (a[j] > 9) {
				a[j + 1] += a[j] / 10;
				a[j] %= 10;
			}
			if (b[j] > 9) {
				b[j + 1] += b[j] / 10;
				b[j] %= 10;
			}
		}
	}
	for (int i = len - 1; i > 0 && b[i] == 0; i--) {
		len--;
	}
	for (int i = len - 1; i >= 0; i--) {
		cout << b[i];
	}
	return 0;
}

7.总结

好啦,我就大概将这几种高精度算法,我也只是提供一个基本思路,还要挺多变化的形式,不过基本的思路是不变的,大家可以以此类推,希望这篇文章能让大家更好的理解高精度这一算法,学算法就是把它当作闯关打怪一样,得培养出兴趣,不然学起来真的很难很累,慢慢来吧,大家都加油!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值