在写题的过程中我们还是会经常遇到高精度这类问题,而在面对这一问题时,没有接触和了解这个算法的同学,可能会比较懵,不知如何下手,这篇文章就较为具体的为大家介绍一下高精度问题。
1.简介
高精度问题通常指的是处理超出标准数据类型(如int
、long
等)所能表示的范围的数值计算问题。由于计算机中的整数类型有固定的位数和范围限制,当需要进行大数运算或者高精度计算时,标准数据类型高精度问题常见于以下场景:
大数加法:多个大数相加时,结果也可能超出标准整数类型的最大值。
大数乘法:当两个大数相乘时,结果可能超出int
或long
的最大值。
大数阶乘:在某些算法中,可能需要进行大数除法,以得到精确的商和余数。
大数幂运算:计算一个数的幂次方,结果可能是一个非常大的数。
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.总结
好啦,我就大概将这几种高精度算法,我也只是提供一个基本思路,还要挺多变化的形式,不过基本的思路是不变的,大家可以以此类推,希望这篇文章能让大家更好的理解高精度这一算法,学算法就是把它当作闯关打怪一样,得培养出兴趣,不然学起来真的很难很累,慢慢来吧,大家都加油!