算法学习笔记(4)——高精度
高精度
高精度加法
题目描述
给定两个正整数(不含前导 0),计算它们的和。
输入格式
共两行,每行包含一个整数。
输出格式
共一行,包含所求的和。
数据范围
\(1 \le\) 整数长度 \(\le 100000\)
输入样例:
12
23
输出样例:
35
要计算两个高精度整数 \(A\) 和 \(B\) 的和,可以用列竖式的思想,从最低位开始,将低位加起来然后进位,每次将进位 \(t\) 都和两者尚未计算的最低位 \(A[i]\) 和 \(B[i]\) 加起来,其除以 \(10\) 的余数就是这一位的值,商就是进位 \(t\),所以:
由于 \(A\) 和 \(B\) 的位数不一定是一样的,这里不妨假设 \(A\) 的长度总是 > = B >=B >=B的长度(如果输入时不满足,就交换 \(A\) 和 \(B\) 再做高精度加法),这样计算的时候就只要把 \(A\) 的每一项都加完,相应的位置如果 \(B[i]\) 还存在就加,不存在就不用加了。
在加完之后还要判断一下进位 \(t\),如果还有进位的话,就要再在结果中加一位。只要用一个if判断一次就可以了,不需要用while来做,因为最后 \(t\) 的值一定是 $ 0 \leq 9 < 10$ 的,因为以 \(A\) 的长度,加上一个不超过其长度的 \(B\),结果 \(C\) 最大能达到的长度也就是 \(A\) 的长度 \(+1\) ,不可能更长了。
由于是从最低位往高位一位一位加,而读取数据的时候是从高位向低位读的,所以这里将 \(A\) 和 \(B\) 都排成从低位向高位的,这样方便计算。相应地,得到的 \(C\) 也是从低位到高位排的,最后记得翻转一下。
#include <iostream>
#include <vector>
using namespace std;
vector<int> add(vector<int> &A, vector<int> &B)
{
vector<int> sum;
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];
sum.push_back(t % 10);
t /= 10;
}
// 处理最后的进位
if (t) sum.push_back(t);
return sum;
}
int main()
{
string a, b;
cin >> a >> b;
// 倒着存
vector<int> A, B;
for (int i = a.size() - 1; i >= 0; i -- ) A.push_back(a[i] - '0');
for (int i = b.size() - 1; i >= 0; i -- ) B.push_back(b[i] - '0');
auto res = add(A, B);
for (int i = res.size() - 1; i >= 0; i -- ) cout << res[i];
puts("");
return 0;
}
高精度减法
题目描述
给定两个正整数(不含前导 0),计算它们的差,计算结果可能为负数。
输入格式
共两行,每行包含一个整数。
输出格式
共一行,包含所求的差。
数据范围
\(1 \le\) 整数长度 \(\le 10^5\)
输入样例:
32
11
输出样例:
21
要计算两个高精度整数 \(A\) 和 \(B\) 的差,也是用列竖式的思想。这里需要先判断一下 \(A\) 和 \(B\) 哪个大,如果 \(B\) 比 \(A\) 大,那么 \(A-B\) 的结果是 \(-(B-A)\),所以只需要实现一个较大的自然数减去一个较小的自然数这个过程就可以了。
在做减法的时候,已经确定传入的 \(A\) 是 \(\ge B\) 的了,所以 \(A\) 的长度也是 \(\ge B\) 的,可以用和前面计算高精度加法时候类似的思想。从低位到高位遍历 \(A\) 中的每一位,用 \(t\) 表示来自上一位的借位(借位只能是 \(0\) 或者 \(1\) ) 。
那么这一位的减法实际上就是 \(A[i] - t - B[i]\)(如果 \(B\) 已经遍历完了,就不用减 \(B[i]\))。如果这个值是负的,那么就说明这一位的减法是向更高一位借了 \(1\) 的,那么下一轮 \(t\) 就要变成 \(1\) ,否则下一轮 \(t\) 的值就是 \(0\) 。
向高位借 \(1\) 的效果就是在本位加了一个 \(10\),所以本位减法的计算结果可以直接用 \(+10\) 之后再 \(\bmod 10\) 来计算,这样不论有没有借位计算的都是正确的。
需要注意,最终的减法结果可能存在前导 \(0\) ,所以需要删除高位的连续 \(0\) 。然而当减法结果是 \(0\) 时候,需要保留一个 \(0\) ,所以在删除高位 \(0\) 的时候注意一下只剩一个数的时候也要停止删除了。
#include <iostream>
#include <vector>
using namespace std;
bool cmp(vector<int> &A, vector<int> &B)
{
// 都是正数,长度越长值越大
if (A.size() != B.size()) return A.size() > B.size();
// 由于是倒着存的,所以从后(高位)往前(低位)开始比较
for (int i = A.size() - 1; i >= 0; i -- )
if (A[i] != B[i])
return A[i] > B[i];
// A=B的情况,返回true
return true;
}
// 计算A - B, 默认A >= B
vector<int> sub(vector<int> &A, vector<int> &B)
{
vector<int> res;
int t = 0; // 减法借位
for (int i = 0; i < A.size(); i ++ ) {
// 被减数减去上次的借位
t = A[i] - t;
// 减数存在则减去
if (i < B.size()) t -= B[i];
// 将差值存入答案
res.push_back((t + 10) % 10);
// 判断处理借位
if (t < 0) t = 1;
else t = 0;
}
// 处理前导零
while (res.size() > 1 && res.back() == 0) res.pop_back();
return res;
}
int main()
{
string a, b;
cin >> a >> b;
vector<int> A, B;
for (int i = a.size() - 1; i >= 0; i -- ) A.push_back(a[i] - '0');
for (int i = b.size() - 1; i >= 0; i -- ) B.push_back(b[i] - '0');
if (cmp(A, B)) {
auto res = sub(A, B);
for (int i = res.size() - 1; i >= 0; i -- ) cout << res[i];
puts("");
}
else {
auto res = sub(B, A);
cout << "-";
for (int i = res.size() - 1; i >= 0; i -- ) cout << res[i];
puts("");
}
return 0;
}
高精度乘法
题目描述
给定两个非负整数(不含前导 0)\(A\) 和 \(B\) ,请你计算 \(A×B\) 的值。
输入格式
共两行,第一行包含整数 \(A\),第二行包含整数 \(B\)。
输出格式
共一行,包含 \(A×B\) 的值。
数据范围
\(1 \le\) A的长度 \(\le 10^5\)
\(0 \le B \le 10000\)
输入样例:
2
3
输出样例:
6
这里 \(A\) 是一个高精度整数, \(b\) 是一个普通精度的整数,计算它们的乘法也是用列竖式的方法,但是因为 \(b\) 不是一个高精度的数,只需要从 \(A\) 低位到高位,每次把对应位置的数字 \(A[i]\) 乘以 \(b\) 再加上来自低位的进位 \(t\) 就可以了,而不需要把 \(b\) 的每一位拆开来。
乘的结果模 \(10\) 就是结果 \(C\) 的这一位上的数,而除以 \(10\) 的结果就是进位。在 \(A\) 的每一位都乘完之后,进位的值可能还没用完,所以要重复“模 \(10\) 放在下一位,除以 \(10\) 作为进位”这个操作。
结果 \(C\) 中的高位可能有连续的 \(0\) ,所以还是像高精度减法中那样处理一下高位的连续 \(0\) ,同时注意如果结果就是 \(0\) 要留下一个 \(0\) 。
#include <iostream>
#include <vector>
using namespace std;
vector<int> mul(vector<int> &A, int &b)
{
vector<int> C;
int t = 0; // 乘法进位
for (int i = 0; i < A.size(); i ++ ) {
t += A[i] * b;
C.push_back(t % 10);
t /= 10;
}
if (t) C.push_back(t);
while (C.size() > 1 && C.back() == 0) C.pop_back();
return C;
}
int main()
{
string a;
int b;
cin >> a >> b;
vector<int> A;
for (int i = a.size() - 1; i >= 0; i -- ) A.push_back(a[i] - '0');
auto C = mul(A, b);
for (int i = C.size() - 1; i >= 0; i -- ) cout << C[i];
return 0;
}
高精度除法
题目描述
给定两个非负整数(不含前导 \(0\)) \(A\),\(B\),请你计算 \(A/B\) 的商和余数。
输入格式
共两行,第一行包含整数 \(A\),第二行包含整数 \(B\)。
输出格式
共两行,第一行输出所求的商,第二行输出所求余数。
数据范围
\(1 \le\) A的长度 \(\le 10^5\)
\(0 \le B \le 10000\)
\(B\) 一定不为 \(0\)。
输入样例:
7
2
输出样例:
3
1
这里 \(A\) 是一个高精度整数, \(b\) 是一个普通精度的整数,计算它们的除法也是用列竖式的方法,但是因为 \(b\) 不是一个高精度的数,只需要从 \(A\) 高位到低位(注意这里和前面都不一样),每次把对应位置的数字 \(A[i]\) 加上来自高位的余数 \(r * 10\) 再除以 \(b\) 就可以了,而不需要把 \(b\) 的每一位拆开来。
这里高位的余数 \(r\) 是在高位那个量级上的,所以在这一位(较低一位)上要乘以 \(10\) 才行。
和前面保持一致, \(A\) 还是从低位到高位来存,但是计算的时候从高位向低位依次做除法,得到的结果 \(C\) 是从高位到低位存的。所以还要记得把 \(C\) 用reverse
翻转一下,变成从低位到高位存的,然后还是要删除一下高位的连续 \(0\) 。
在代码实现中,函数div
的返回值返回的是 \(A\) 除以 \(b\) 的商,而余数 \(r\) 从参数表里用引用的形式返回。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// 返回A/b的值,余数c通过参数列表引用形式返回
vector<int> div(vector<int> &A, int &b, int &c)
{
vector<int> C;
c = 0;
// 从高位开始做除法
for (int i = A.size() - 1; i >= 0; i -- ) {
c = c * 10 + A[i];
C.push_back(c / b);
c %= b;
}
reverse(C.begin(), C.end());
while (C.size() > 1 && C.back() == 0) C.pop_back();
return C;
}
int main()
{
string a;
int b;
cin >> a >> b;
vector<int> A;
for (int i = a.size() - 1; i >= 0; i -- ) A.push_back(a[i] - '0');
int c;
auto C = div(A, b, c);
for (int i = C.size() - 1; i >= 0; i -- ) cout << C[i];
puts("");
cout << c << endl;
return 0;
}