本文是刷算法记录的第一篇,是按照ACWING的算法课的顺序来的,写一下题解以供参考。
一、问题描述
1.高精度加法
给定两个正整数(不含前导 00),计算它们的和。
输入格式
共两行,每行包含一个整数。
输出格式
共一行,包含所求的和。
数据范围
1≤整数长度≤100000
2.高精度减法
给定两个正整数(不含前导 00),计算它们的差,计算结果可能为负数。
输入格式
共两行,每行包含一个整数。
输出格式
共一行,包含所求的差。
数据范围
1≤整数长度≤105
3.高精度乘法
给定两个非负整数(不含前导 00) A 和 B,请你计算 A×B的值。
输入格式
共两行,第一行包含整数 A,第二行包含整数 B。
输出格式
共一行,包含 A×B 的值。
数据范围
1≤A的长度≤100000
0≤B≤10000
4.高精度除法
给定两个非负整数(不含前导 00) A,B,请你计算 A/B 的商和余数。
输入格式
共两行,第一行包含整数 A,第二行包含整数 B。
输出格式
共两行,第一行输出所求的商,第二行输出所求余数。
数据范围
1≤A的长度≤100000,
1≤B≤10000,
B一定不为 0
二、算法分析
高精度问题仅在C++语言中存在,在JAVA和Python语言中并不存在。在处理高精度问题时,要注意以下几个问题:
1.高精度的模拟。高精度由于输入数据和存储数据过大,无法用C++中的数据类型进行存储和表示,故利用数组进行存储。在利用数组进行存储后,对加减乘除的模拟更接近于传统的手算模拟,而加减乘都是从个位开始运算,除法从最高位开始运算,所以我们将最低位存储在数组最低位(a[0]),进行模拟运算。
2.前导0问题:前导0指,最后的计算结果中,在最高位出现连0的情况,例如000012312的情况,此时需要处理掉前导0。在加法中,显然不会出现前导0的问题;减法和除法会出现前导0的问题;乘法唯一可能出现前导0的情况是,000000000,即0。在对前导0处理时,应该从最高位开始处理,直到最后一位。
3.算法流程:
(1)输入:将大数倒置保存在数组中。在读入大数时,我们用字符串进行读入,输入数组时,由于字符串的单个元素为char型,在输入时需要进行“a[i]-'0'”的操作,完成隐式类型转换。
(2)运算:进行竖式计算的模拟,注意对前导0进行处理。
(3)输出:输出即可。
三、代码实现
1.高精度加法
源代码
#include<iostream>
#include<vector>
using namespace std;
vector<int> add(vector<int> &a,vector<int> &b){
vector<int> c;
//模拟运算,从低位到高位
int t = 0;//进位,我们在高精度运算中,统一把值填进进位,方便统一处理。
for(int i=0;i<a.size()||i<b.size();i++){
if(i<a.size()) t+=a[i];
if(i<b.size()) t+=b[i];
c.push_back(t%10);
t/=10;
}
if(t==1) c.push_back(1);
return c;
}
int main(){
//输入
string s1,s2;
cin>>s1>>s2;
//倒序输入,将末位填入数组的第一位
vector<int> a,b;
for(int i=s1.size()-1;i>=0;i--){
a.push_back(s1[i]-'0');
}
for(int i=s2.size()-1;i>=0;i--){
b.push_back(s2[i]-'0');
}
//计算
auto c = add(a,b);
//输出
for(int i=c.size()-1;i>=0;i--){
cout<<c[i];
}
return 0;
}
解析
1.在计算过程中,我们利用进位变量作为运算的中转单元,完成结果输出与下一级进位的保存。这一思想同样用于其他三种运算,
2.运算要保证两个都算完,这里是利用||来进行判断,加法有可能位数加1,所以要判断最后一位是否输入。
2.高精度减法
源代码
#include<iostream>
#include<vector>
using namespace std;
//判断两个大数的大小
bool cmp(vector<int> &a,vector<int> &b){
//判断逻辑:先比较两个数的位数,若相同,则从最高位起依次比较每一位的大小
if(a.size()<b.size()) return false;
if(a.size()>b.size()) return true;
for(int i=a.size()-1;i>=0;i--){
if(a[i]<b[i]) return false;
if(a[i]>b[i]) return true;
}
return true;
}
//大数相减
vector<int> sub(vector<int> &a,vector<int> &b){
vector<int> c;
int t=0;
//从最低位开始依次相减,t这里表示借位,需要在循环开始时减去
for(int i=0;i<a.size();i++){
t=a[i]-t;
if(i<b.size()) t-=b[i];
c.push_back((t+10)%10);
//如果t<0,说明需要借位,t=1,否则t=0
if(t<0) t=1;
else t=0;
}
//去除高位的0
while(c.back()==0&&c.size()>1) c.pop_back();
return c;
}
int main(){
//输入
vector<int> a,b;
string s1,s2;
cin>>s1>>s2;
for(int i=s1.size()-1;i>=0;i--){
a.push_back(s1[i]-'0');
}
for(int i=s2.size()-1;i>=0;i--){
b.push_back(s2[i]-'0');
}
//处理和输出
//利用CMP函数判断大小,如果a>b,则进行处理后还需要添加‘-’号
if(cmp(a,b)) {
auto c=sub(a,b);
for(int i=c.size()-1;i>=0;i--){
cout<<c[i];
}
}
else{
auto c=sub(b,a);
cout<<'-';
for(int i=c.size()-1;i>=0;i--){
cout<<c[i];
}
}
return 0;
}
解析
1.在计算过程中,我们利用借位变量作为运算的中转单元,完成结果输出与下一级借位的保存。不同于加法的进位,减法的借位需要在循环开始时剪掉。在运算完成后,借位为负则借位为1.
2.减法需要去除前导0.
3.减法需要判断大小,用以调整sub函数和输出负号。
3.高精度乘法
源代码
#include<iostream>
#include<vector>
using namespace std;
vector<int> mul(vector<int> &a, int b){
vector<int> c;
// 高精度乘法,循环终止条件为进位为0且a数组算完
int t = 0;
for(int i = 0; i < a.size() || t; i++){
if(i<a.size()) t+=a[i]*b;
c.push_back(t%10);
t/=10;
}
// 去除前导0
while(c.size() > 1 && c.back() == 0) c.pop_back();
return c;
}
int main(){
// 读入数据
string s;
int b;
cin >> s >> b;
vector<int> a;
for(int i = s.size() - 1; i >= 0; i--) a.push_back(s[i] - '0');
// 高精度乘法
auto c = mul(a, b);
// 输出结果
for(int i = c.size() - 1; i >= 0; i--) cout<<c[i];
return 0;
}
解析
1.以进位为中间单元,直到进位也最终为0.
2.有可能全为0,记得去除前导0。
4.高精度除法
源代码
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
vector<int> div(vector<int> &a, int &b, int &r) {
vector<int> c;
// 高精度除法,循环终止条件为a数组算完
// 除法是从最高位开始算,所以要从数组末端开始
r=0;//余数作为中间运算单元
for(int i=a.size()-1;i>=0;i--){
r=a[i]+ 10*r;
c.push_back(r/b);
r%=b;
}
//处理后得到的结果是高位在数组低位,需要倒转
reverse(c.begin(),c.end());
while (c.size() > 1 && c.back() == 0) c.pop_back();
return c;
}
int main(){
// 读入数据
string s;
vector<int> a;
int b;
cin>>s>>b;
for(int i=s.size()-1;i>=0;i--) a.push_back(s[i]-'0');
// 高精度除法
int r=0;// 余数
auto c=div(a,b,r);
// 输出结果
for(int i=c.size()-1;i>=0;i--) cout<<c[i];
cout<<endl<<r;
return 0;
}
解析
1.在计算过程中,我们利用余数变量作为运算的中转单元,完成结果输出与下一级的保存。
2.余数r利用引用完成余数部分和商部分的同时处理。
5.运算单元汇总
源代码
int t=0
//add
if(i<a.size()) t+=a[i];
if(i<b.size()) t+=b[i];
//sub
t=a[i]-t;
if(i<b.size()) t-=b[i];
//mul
t+=a[i]*b;
//div
t=a[i]+t*10;