基础算法---【高精度算法】
前言
高精度算法解决的就是数值特别大的数据之间的加减乘除的问题。基本思想就是先用字符串存入数据,然后再将这串数字逆序存放在数组里边。利用数组来模拟计算。
为什么要逆序存在数组里边?原因就是我们小学时候的那种列竖式计算加减乘除的时候是先从个位开始计算的,而我们便利数组是从头开始便利的,所以逆序存放对于我们的计算更加的方便。
题目一:高精度加法
https://www.luogu.com.cn/problem/P1601
解题思路
当我们要将数据存放在数组里边的时候,要注意一个规律,请看下边的例子。
string s = "123";
int arr[] = {3,2,1};
上边字符串的长度为3,很容易就可以知道,字符串里边下标为0的位置的 ‘1’ 在数组里边是在下标为2的地方,而字符串里边下标为1的 ‘2’ 在数组里边是下标为1的位置。
仔细一想,0 + 2 == 2,1 + 1 == 2,因此我们就可以得到一个计算方式,字符串里边下标为i位置存放的数据对应存放在数组里边的下标为n - 1 - i(其中n为字符串的长度,n - 1就是字符串里边最大的下标)
在将字符串里边读到的一个个字符数据存入数组的时候是需要减去'0'的,因为字符串里边的一个个数据是字符,而数组里边的一个个数据是数字,字符转化为数字就要减去'0',本质上就是ASCII码值相减的结果。
当我们顺利的把数据存到数组里边的时候,我们就可以进行模拟加法运算了。
在模拟加法运算的时候,我们需要三个数组a,b,c,其中a数组和b数组存放的是要进行加法运算的两个数,而c数组存的就是最终的结果。
最后计算结束之后就直接打印数组c就可以得到我们的结果了。
代码实现
#include<iostream>
using namespace std;//题目描述里边的要读入的数据的位数为500,数组的大小可以拓展到1e6级别,高精度算法是支持的
const int N = 1e6 + 10;
int a[N], b[N], c[N];
int la, lb, lc;//表示的是数组的实际大小string x, y;//读入数字//高精度算法模板
void add(int c[], int a[], int b[])
{for (int i = 0; i < lc; i++){c[i] += a[i] + b[i];//用 += 是为了把c数组里边进位的结果也要一起加进去//处理进位c[i + 1] = c[i] / 10;//处理余数c[i] = c[i] % 10;}//考虑最高位发生进位的情况---默认情况下最高位是0,if判断条件是进不去的if (c[lc]){lc++;}
}int main()
{//1.读入数字cin >> x >> y;//2.将数字逆序存入数组//数组的实际大小根据读入的数字来定la = x.size();lb = y.size();lc = max(la, lb);//取两个数字的最大值(最高位发生进位再另外考虑)for (int i = 0; i < la; i++){a[la - 1 - i] = x[i] - '0';}for (int i = 0; i < lb; i++){b[lb - 1 - i] = y[i] - '0';}//进行模拟加法add(c, a, b);// c = a + b;//最后输出结果--逆序存放也要逆序打印for (int i = lc - 1; i >= 0; i--){cout << c[i];}return 0;
}
题目二:高精度减法
https://www.luogu.com.cn/problem/P2142
解题思路
基本思路还是字符串读入然后逆序存放在数组里边,再进行数学列竖式计算减法,对于两个数相减,是会存在减数大于被减数的情况的。如果直接列竖式相减就不符合逻辑,所以要先判断读入的两个数字的大小关系,从而需要保证被减数大于减数,然后就可以进行列竖式减法运算了。具体细节结合代码注释给出。
对于比较被减数和减数的大小,由于是以字符串的形式读入的数字,因此如果直接比较就是字典序的比较,比如111和99比较,由于9的ASCII大于1的,所以导致直接比较出来111<99。这显然与我们想要的事实不符,所以我们可以采取先长度比较再字典序比较的方式,对于两个长度不一样长的字符串来说,长度长的对应的数字肯定是要大于长度短的,而如果两个字符串一样长的话,直接就以字典序的方式比较就可以了。
代码实现
#include<iostream>
using namespace std;const int N = 1e6 + 10;
int a[N], b[N], c[N];
int la, lb, lc;string x, y;//比较被减数和减数的大小
bool cmp(string& x, string& y)
{if (x.size() != y.size()){return x.size() < y.size();}else{return x < y;}
}//高精度减法模板
void sub(int c[],int a[],int b[])
{for (int i = 0; i < lc; i++){c[i] += a[i] - b[i];//用 += 是为了处理不够减向前借位的情况if (c[i] < 0)//借位{c[i + 1] -= 1;c[i] += 10;}}//处理边界情况while(lc > 1 && c[lc - 1] == 0){lc--;}
}int main()
{//1.读入cin >> x >> y;//2.判断x,y的大小if (cmp(x, y))//如果x < y,就返回true{swap(x, y);cout << "-";}//3.逆序存入数组la = x.size();lb = y.size();lc = max(la, lb);//边界情况最后处理for (int i = 0; i < la; i++){a[la - 1 - i] = x[i] - '0';}for (int i = 0; i < lb; i++){b[lb - 1 - i] = y[i] - '0';}//4.列竖式减法运算sub(c, a, b);//c = a - b//5.打印结果for (int i = lc - 1; i >= 0; i--){cout << c[i];}return 0;
}
对于边界情况的处理,再详细说明一下。在进行减法的过程当中是会出现高位变成0的情况的,就像999 - 991,此时那些高位的0是不能算在lc里边的,所以lc--,但是有特殊情况就是如果是999 - 999,按照上边的做法,lc最后会变成0,可是正确的结果应该是0,所以还要加个lc > 1的判断。
题目三:高精度乘法
https://www.luogu.com.cn/problem/P1303
解题思路
第一步还是字符串读入,逆序存在数组里边。如果用常规的列竖式计算乘法,那么每一次相乘都要处理进位,有点太麻烦了,因此,我们在这里采用无进位相乘,请看下边的图示。
最终结果所处在c数组(存放最终运算结果的数组)里边下标的位置通过找规律就可以发现,a数组下标为i的位置和b数组里边下标为j的位置对应相乘之后的结果在c数组里边是下标i + j的位置。注意上边图片里边模拟的乘法过程是还没有逆序存在数组里边的样子,因此我们找下标规律的时候是最右边下标从0开始。
代码实现
#include<iostream>
using namespace std;const int N = 1e6 + 10;
int a[N], b[N], c[N];
int la, lb, lc;string x, y;//高精度乘法模板
void mul(int c[], int a[], int b[])
{//无进位相乘---利用嵌套循环(取a数组的每一位和b数组的每一位相乘)for (int i = 0; i < la; i++){for (int j = 0; j < lb; j++){c[i + j] += a[i] * b[j];//用 += 是因为全部算出来了之后要累加}}//进位---跟高精度加法的时候的进位类似for (int i = 0; i < lc; i++){c[i + 1] += c[i] / 10;c[i] = c[i] % 10;}//处理最高位为0的情况---有可能是 0 * 999 == 0这种情况,跟高精度减法那里一样处理while(lc > 1 && c[lc - 1] == 0){lc--;}
}int main()
{//1.读入cin >> x >> y;//2.逆序存放在数组里边la = x.size();lb = y.size();lc = la + lb;//两数相乘的结果不会超过两个数的位数之和for (int i = 0; i < la; i++){a[la - 1 - i] = x[i] - '0';}for (int i = 0; i < lb; i++){b[lb - 1 - i] = y[i] - '0';}//3.高精度乘法mul(c, a, b);// c = a * b//4.打印结果for (int i = lc - 1; i >= 0; i--){cout << c[i];}return 0;
}
题目四:高精度除法
注意这里提到的高精度除法其实是高精度除以低精度。高精度除以高精度的情况非常的少见,就不做介绍。
https://www.luogu.com.cn/problem/P1480
解题思路
我们只需要字符串读入,数组逆向存入那个高精度的数就可以了,剩下的一个数用内置类型来存储就可以了。接着就是模拟列竖式除法的过程。请看下边的示例图。
代码实现
#include<iostream>
using namespace std;const int N = 1e6 + 10;
int a[N], b, c[N];
int la, lc;//高精度除法(高精度 / 低精度)
void sub(int c[], int a[], int b)
{long long t = 0;//int类型是有可能存不下余数的//除法是从最高位开始除的,所以要倒着便利数组afor (int i = la - 1; i >= 0; i--){//计算被除数t = t * 10 + a[i];//商存在c数组里c[i] = t / b;//计算余数t = t % b;}//处理最高位为0的情况while (lc > 1 && c[lc - 1] == 0){lc--;}
}int main()
{//1.读入并且逆序存入数组string x;cin >> x >> b;la = x.size();lc = la;for (int i = 0; i < la; i++){a[la - 1 - i] = x[i] - '0';}//2.高精度除法运算sub(c, a, b);// c = a / b//3.打印结果for (int i = lc - 1; i >= 0; i--){cout << c[i];}return 0;
}