【基础算法】高精度(加、减、乘、除)

文章目录

什么是高精度

我们平时使用加减乘除的时候都是直接使用 + - * / 这些符号,前提是进行运算的数字在一定的范围之内。一旦这个数字非常大 的时候,比如 10 10086 10^{10086} 1010086 这个样一个天文数字,普通的 intlong long 这些类型根本容不下它,像用它进行运算就更不可能了,所以这个时候我们就需要用到高精度算法来计算加减乘除。

高精度算法的核心有两步:

  1. 先用字符串 读入这个数,然后用数组逆序存储该数的每⼀位;

  2. 利用数组,模拟小学就学过的加减乘除竖式运算过程。


1. 高精度加法

【题目链接】

P1601 A+B Problem(高精) - 洛谷

【题目描述】

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

【输入格式】

分两行输入。 a , b ≤ 10 500 a,b \leq 10^{500} a,b≤10500。

【输出格式】

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

【示例一】

输入

复制代码
1
1

输出

复制代码
2

【示例二】

输入

复制代码
1001
9099

输出

复制代码
10100

【说明/提示】

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

40 % 40\% 40% 的测试数据, 0 ≤ a , b ≤ 10 18 0\le a,b \le10^{18} 0≤a,b≤1018。


解题思路

  1. 先用字符串 读入这个数,然后用数组逆序存储该数的每⼀位

我们可以在全局上设置三个数组 a[], b[], c[],分别用于存储加法运算的左操作数、右操作数以及左后的结果的每一位(逆序)。比如 456 + 789 = 1245 456 + 789 = 1245 456+789=1245,对应 a[] = {6, 5, 4}, b = {9, 8, 7}, c = {5, 4, 2, 1}。同时,设置三个变量 la, lb, lc 用于记录数字的长度。

cpp 复制代码
const int N = 1e6 + 10;
int a[N], b[N], c[N];
int la, lb, lc;

为什么要逆序?这是因为我们在人为计算竖式的时候是按照从个位到最高位去计算的,相当于逆序计算。当我们逆序存储后之后在模拟竖式计算的过程中就可以顺序遍历数组进行操作。

有了这几个变量之后就可以存储数字了,这个过程中,字符串的最后一位应存储在数组的第一位,以此类推。注意字符串中的每一位存储的是字符,而数组中存储的是数,所以在搬运各个位的时候要减去 '0' 转化成对应的数字。

cpp 复制代码
string x, y;
cin >> x >> y;
la = x.size(), lb = y.size(), lc = max(la, lb); // 这里先暂时写成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';
  1. 利用数组,模拟小学就学过的加减乘除竖式运算过程

用循环遍历数组,填写 c[] 中的每一位。

cpp 复制代码
for(int i = 0; i < lc; ++i)
{
    c[i] += a[i] + b[i];  // 对应位相加,再加上进位(+=实际就是加上进位)
    c[i + 1] = c[i] / 10;  // 进到下一位去
    c[i] %= 10;  // 当前位对应的数
}

代码实现

cpp 复制代码
#include<iostream>

using namespace std;

const int N = 1e6 + 10;
int a[N], b[N], c[N];
int la, lb, lc;

// 高精度加法
void add(int c[], int a[], int b[])
{
    // 模拟竖式运算
    for(int i = 0; i < lc; ++i)
    {
        c[i] += a[i] + b[i];  // 对应位相加,再加上进位
        c[i + 1] = c[i] / 10;  // 处理进位
        c[i] %= 10;  // 当前位对应的数
    }

    // 如果最后一位进位了,那么c的长度+1
    if(c[lc]) ++lc;
}

int main()
{
    string x, y;
    cin >> x >> y;

    // 拆分每一位数字,并逆序放在数组中
    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';

    // 高精度加法:c = a + b
    add(c, a, b);

    // 逆序输出
    for(int i = lc - 1; i >= 0; --i) cout << c[i];
    
    return 0;
}

2. 高精度减法

【题目链接】

P2142 高精度减法 - 洛谷

【题目描述】

高精度减法。

【输入格式】

两个整数 a , b a,b a,b(第二个可能比第一个大)。

【输出格式】

结果(是负数要输出负号)。

【示例一】

输入

复制代码
2
1

输出

复制代码
1

【说明/提示】

  • 20 % 20\% 20% 数据 a , b a,b a,b 在 long long 范围内;
  • 100 % 100\% 100% 数据 0 < a , b ≤ 10 10086 0<a,b\le 10^{10086} 0<a,b≤1010086。

解题思路

和高精度加法很类似,只不过这里要处理两个细节

  1. 结果可能为负数

x - yxy 小的时候,结果为负数,我们可以交换 xy 的值然后在计算的结果后加上 - 即可。

而由于 xy 是字符串,所以判断 xy 的 "大小" 时需要考虑两个点:

  • 字符串长度长的对应的数一定更大。

  • 如果字符串长度一样,那么从前往后比较每一位的字典序(直接用 <> 比较即可)。

  1. 可能出现前导 0

我们最终输出的数的位数由 lc 决定,如果我们让 lc 的值等于 max(la, lb) 而不做处理的话下面的运算结果就为 078 而不是 78

所以这个时候我们就需要更新 lc,可以考虑从 c[] 数组的 c[lc - 1] 位置开始往前遍历,直到遇到第一个不是 0 的位置或者lc == 0 的时候结束,在这个过程中不断让 lc 减一。

cpp 复制代码
while(lc > 1 && c[lc - 1] == 0) --lc;  // 处理前导0

注意不能是 lc > 0,因为有可能两数相等相减的最终结果为 0。


代码实现

cpp 复制代码
#include<iostream>

using namespace std;

const int N = 1e6 + 10;

int a[N], b[N], c[N];
int la, lb, lc;

// 高精度减法
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;  // 处理前导0
}

int main()
{
    string x, y;
    cin >> x >> y;

    // 计算 x - y
    // 如果 x 对应的数比 y 对应的数小,那么交换二者,并在结果前加上负号
    if(x.size() < y.size()
    || (x.size() == y.size() && x < y))
    {
        cout << '-';
        swap(x, y);
    }

    // 拆分每一位数字,并逆序放在数组中
    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';

    sub(c, a, b);
    
    for(int i = lc - 1; i >= 0; --i) cout << c[i];
    
    return 0;
}

3. 高精度乘法

【题目链接】

P1303 A*B Problem - 洛谷

【题目描述】

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

【输入格式】

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

【输出格式】

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

【示例一】

输入

复制代码
1 
2

输出

复制代码
2

【说明/提示】

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


解题思路

乘法也可以模拟普通竖式运算过程,但是这样会频繁地处理进位,因为我们也可以采取另一种方式:无进位相乘,相加到对应位上,最后再统一处理进位

通过下标的对应关系我们可以发现, a[] 的第 i 个位置的数与 b[] 的第 j 个位置的数相乘恰好就加在 c[] 的第 i + j 位置上。

cpp 复制代码
for(int i = 0; i < la; ++i)
    for(int j = 0; j < lb; ++j)
        c[i + j] += a[i] * b[j];

当我们相加之后,再来处理进位。

cpp 复制代码
for(int i = 0; i < lc; ++i)
{
    c[i + 1] += c[i] / 10;
    c[i] %= 10;
}

同样可能出现前导 0 的问题,比如 10000 * 0,于是我们采用相同的方法处理:

cpp 复制代码
while(lc > 1 && c[lc - 1] == 0) --lc;

代码实现

cpp 复制代码
#include<iostream>

using namespace std;

const int N = 1e6 + 10;
int a[N], b[N], c[N];
int la, lb, lc;

void mul(int c[], int a[], int 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] %= 10;
    }
	
    // 处理前导0
    while(lc > 1 && c[lc - 1] == 0) --lc;
}

int main()
{
    string x, y;
    cin >> x >> y;

    la = x.size(), lb = y.size(), lc = la + lb;  // 两数相乘长度最多不超过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';

    mul(c, a, b);

    for(int i = lc - 1; i >= 0; --i) cout << c[i];

    return 0;
}

4. 高精度除法 (高精度 / 低精度)

【题目链接】

P1480 A/B Problem - 洛谷

【题目描述】

输入两个整数 a , b a,b a,b,输出它们的商。

【输入格式】

两行,第一行是被除数,第二行是除数。

【输出格式】

一行,商的整数部分。

【示例一】

输入

复制代码
10
2

输出

复制代码
5

【说明/提示】

0 ≤ a ≤ 10 5000 0\le a\le 10^{5000} 0≤a≤105000, 1 ≤ b ≤ 10 9 1\le b\le 10^9 1≤b≤109。


解题思路

由于遇到较多的一般是高精度 / 低精度,所以这里就只讲这种情况。

模拟除法的竖式运算时,实际上会用到多次除法运算,我们可以用一个变量 t 来记录每次的被除数,除出来的商放在 c[] 中的对应位置,然后更新 t,总共除 la 次。最后处理前导 0 即可。

注意数据范围,b 最大是 10 9 10^9 109,而 t 可能是 b 的几倍,所以 t 可以用 long long 来存储。


代码实现

cpp 复制代码
#include<iostream>

using namespace std;

typedef long long LL;

const int N = 1e6 + 10;
int a[N], b, c[N];
int la, lc;

// 高精度除法(高精度 / 低精度)
void div(int c[], int a[], int b)
{
    LL t = 0;
    for(int i = la - 1; i >= 0; --i)
    {
        t = t * 10 + a[i];  // 计算当前的被除数
        c[i] = t / b;
        t %= b;
    }

    // 处理前导0
    while(lc > 1 && c[lc - 1] == 0) --lc;
}

int main()
{
    string x; cin >> x;
    int b; cin >> b;

    la = x.size(), lc = la;
    for(int i = 0; i < la; ++i) a[la - 1 - i] = x[i] - '0'; 

    div(c, a, b);

    for(int i = lc - 1; i >= 0; --i) cout << c[i];

    return 0;
}
相关推荐
showmeyourcode0.o26 分钟前
QT常用控件(1)
开发语言·c++·qt
不二狗33 分钟前
每日算法 -【Swift 算法】查找字符串数组中的最长公共前缀
开发语言·算法·swift
不二狗37 分钟前
每日算法 -【Swift 算法】将整数转换为罗马数字
开发语言·算法·swift
小黄人软件41 分钟前
C++ Visual Studio 2017厂商给的源码没有.sln文件 易兆微芯片下载工具加开机动画下载。
开发语言·c++·visual studio
Moonbit1 小时前
双周报Vol.73:移除使用方法实现 trait 、新增了 “错误多态” 功能、.语法支持使用 _ 的匿名函数...
后端·算法
chao_7891 小时前
链表题解——反转链表【LeetCode】
开发语言·python·算法
AI迅剑1 小时前
模块二:C++核心能力进阶(5篇)篇二:《多线程编程:C++线程池与原子操作实战》(14万字深度指南)
java·开发语言·c++
oioihoii1 小时前
C++中锁与原子操作的区别及取舍策略
java·开发语言·c++
Code_流苏2 小时前
Python趣学篇:从零打造智能AI井字棋游戏(Python + Tkinter + Minimax算法)
python·算法·游戏·tkinter·智能井字棋·minimax算法
理智的灰太狼2 小时前
题目 3230: 蓝桥杯2024年第十五届省赛真题-星际旅行
算法·职场和发展·蓝桥杯