高精度加减乘除各类题目

太久没有写这类题型,都快忘的差不多了,总结一下。

高精度

高精度,也叫大整数计算,是指处理大数字的数学计算方法。在一般的科学计算中,经常会遇到需要计算小数点后几百位甚至更多的数字,或者是几千亿几百亿、几千几万位的大数字。这类数字无法用基本类型进行存储,因此需要使用高精度算法。

高精度算法是用计算机对于超大数据的一种模拟加、减、乘、除、乘方、阶乘、开方等运算。对于非常庞大的数字无法在计算机中正常存储,于是,将这个数字拆开,拆成一位一位的,或者是四位四位的存储到一个数组中,用一个数组去表示一个数字,这样这个数字就被称为是高精度数。高精度算法就是能处理高精度数各种运算的算法。

加法(A+B Problem(高精))

题目描述

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

输入格式

分两行输入。a,b≤10500。

输出格式

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

输入输出样例

输入 #1复制

复制代码
1
1

输出 #1复制

复制代码
2

输入 #2复制

复制代码
1001
9099

输出 #2复制

复制代码
10100

说明/提示

20%20% 的测试数据,00≤a,b≤109;

40%40% 的测试数据,0≤a,b≤1018。

代码加注释:

cpp 复制代码
#include<stdio.h> // 引入标准输入输出库  
#include<string.h> // 引入字符串处理库  
#include<algorithm> // 引入算法库,用于使用max函数  
using namespace std; // 使用标准命名空间  
  
int main()  
{  
    char s1[100], s2[100]; // 定义两个字符数组,用于存储输入的字符串  
    int a[100], b[100], c[100]; // 定义三个整数数组,用于存储转换后的数字以及相加的结果  
  
    scanf("%s", s1); // 从标准输入读取字符串s1  
    scanf("%s", s2); // 从标准输入读取字符串s2  
  
    memset(a, 0, sizeof(a)); // 将数组a初始化为0  
    memset(b, 0, sizeof(b)); // 将数组b初始化为0  
    memset(c, 0, sizeof(c)); // 将数组c初始化为0  
  
    int len1 = strlen(s1); // 获取字符串s1的长度  
    int len2 = strlen(s2); // 获取字符串s2的长度  
  
    // 将字符串s1逆序存入数组a中  
    for (int i = 0, j = len1 - 1; i < len1; i++, j--) {  
        a[i] = s1[j] - '0'; // 字符转换为数字,'0'的ASCII码为48,故用'0'而不是48  
    }  
  
    // 将字符串s2逆序存入数组b中  
    for (int i = 0, j = len2 - 1; i < len2; i++, j--) {  
        b[i] = s2[j] - '0'; // 同上,字符转换为数字  
    }  
  
    int len = max(len1, len2); // 取两个字符串长度的最大值作为相加后数组c的长度  
  
    // 相加操作  
    for (int i = 0; i < len; i++) {  
        c[i] += a[i] + b[i]; // 每一位相加  
        c[i + 1] += c[i] / 10; // 进位处理  
        c[i] %= 10; // 取个位数  
    }  
  
    int i = 99; // 从数组c的末尾开始  
    while (i > 0 && c[i] == 0) { // 去除前导0  
        i--;  
    }  
  
    // 打印结果  
    for (int j = i; j >= 0; j--) {  
        printf("%d", c[j]);  
    }  
  
    return 0; // 程序正常退出  
}

减法(高精度减法)

题目描述

高精度减法。

输入格式

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

输出格式

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

输入输出样例

输入 #1复制

cpp 复制代码
2
1

输出 #1复制

cpp 复制代码
1

说明/提示

  • 20% 数据 a,b 在 long long 范围内;
  • 100% 数据 0<a,b≤1010086。

代码加注释:

cpp 复制代码
#include<stdio.h> // 引入标准输入输出库  
#include<string.h> // 引入字符串处理库  
#include<iostream> // 引入C++标准输入输出库(这里用不到,可以去掉)  
using namespace std; // 使用标准命名空间(因为包含了iostream,这里用到了)  
  
int main()  
{  
    char s1[100000], s2[100000]; // 定义两个字符数组,用于存储输入的字符串  
    int a[100000], b[100000], c[100000]; // 定义三个整数数组,用于存储转换后的数字以及相减的结果  
  
    scanf("%s", s1); // 从标准输入读取字符串s1  
    scanf("%s", s2); // 从标准输入读取字符串s2  
  
    memset(a, 0, sizeof(a)); // 将数组a初始化为0  
    memset(b, 0, sizeof(b)); // 将数组b初始化为0  
    memset(c, 0, sizeof(c)); // 将数组c初始化为0  
  
    int len1 = strlen(s1); // 获取字符串s1的长度  
    int len2 = strlen(s2); // 获取字符串s2的长度  
  
    // 将字符串s1逆序存入数组a中  
    for (int i = 0, j = len1 - 1; i < len1; i++, j--) {  
        a[i] = s1[j] - '0'; // 字符转换为数字,使用'0'的ASCII码值进行转换  
    }  
  
    // 将字符串s2逆序存入数组b中  
    for (int i = 0, j = len2 - 1; i < len2; i++, j--) {  
        b[i] = s2[j] - '0'; // 同上  
    }  
  
    int f = 0; // 用于标记哪个数更大,以便决定是相减还是相加(这里实际上是相减)  
    if (len2 > len1) f = 1; // 如果s2更长,则s2更大  
    else if (len2 == len1) {  
        for (int i = 0; i < len1; i++) {  
            if (s2[i] > s1[i]) {  
                f = 1;  
                break;  
            }  
        }  
    }  
  
    if (f == 1) { // 如果s2更大  
        for (int i = 0; i < len2; i++) {  
            c[i] += b[i] - a[i]; // 每一位相减  
            if (c[i] < 0) { // 如果结果为负数,则需要向高位借位  
                c[i] += 10; // 加上10,变成正数  
                b[i + 1]--; // 高位减1,表示借位  
            }  
        }  
  
        // 去除前导0并打印负数结果  
        int i = 99999;  
        while (i > 0 && c[i] == 0) {  
            i--;  
        }  
        printf("-"); // 打印负号  
        for (int j = i; j >= 0; j--) {  
            printf("%d", c[j]);  
        }  
    }  
    else { // 如果s1更大或两者相等(但此时f为0,说明s1更大)  
        for (int i = 0; i < len1; i++) {  
            c[i] += a[i] - b[i]; // 每一位相减  
            if (c[i] < 0) { // 如果结果为负数,则需要向高位借位  
                c[i] += 10; // 加上10,变成正数  
                a[i + 1]--; // 高位减1,表示借位  
            }  
        }  
  
        // 去除前导0并打印结果  
        int i = 99999;  
        while (i > 0 && c[i] == 0) {  
            i--;  
        }  
        for (int j = i; j >= 0; j--) {  
            printf("%d", c[j]);  
        }  
    }  
  
    return 0; // 程序正常退出  
}

乘法(【模板】高精度乘法 | A*B Problem 升级版)

题目背景

本题数据已加强,请使用 FFT 或分治乘法。

题目描述

给你两个正整数 a,b,求 a×b。

输入格式

第一行一个正整数,表示 a;

第二行一个正整数,表示 b。

输出格式

输出一行一个整数表示答案。

输入输出样例

输入 #1复制

cpp 复制代码
83517934
327830610

输出 #1复制

cpp 复制代码
27379735249159740

说明/提示

【数据范围】

1≤a,b≤101000000

可能需要一定程度的常数优化。

数据由 NaCly_Fish 重造

用FFT来解决问题我还没有学会暂时是写不动了。这里简单介绍一下概念。下面的代码主要是高精度乘法模板,没有使用FFT算法。

FFT介绍:

FFT,即快速傅里叶变换(Fast Fourier Transform),是一种高效的计算离散傅里叶变换(DFT)和其逆变换的算法。DFT是将信号从时间域变换到频率域的一种数学工具,广泛应用于信号处理、图像处理、数值分析和物理学等领域。然而,直接计算DFT的复杂度是O(n^2),对于大数据量来说效率很低。FFT通过利用系数W的周期性和对称性,将DFT中一部分重复的计算合并起来,使得算法复杂度降低到O(n log n),从而大大提高了计算效率。

FFT的基本思想是分治策略。它将一个较长的序列(通常是2的整数次幂个数据)的DFT分解成两个较短的序列的DFT,然后再将这两个较短序列的DFT合并成一个较长的序列的DFT。这个过程可以递归进行,直到分解到最短的序列(通常是长度为1或2的序列)为止。

FFT有多种实现方式,如库利-图基(Cooley-Tukey)FFT算法和基2 DIT-FFT算法等。这些算法在具体实现上有所不同,但基本思想都是利用分治策略来降低计算复杂度

分治乘法介绍:

分治乘法是一种利用分治策略 来解决大整数乘法问题的算法。分治策略的基本思想是将一个规模为N的问题分解为K个规模较小的子问题,这些子问题相互独立且与原问题性质相同。求出子问题的解,然后合并这些解,就可以得到原问题的解。

在大整数乘法中,分治策略可以通过将大整数拆分为更小的整数部分,然后分别计算这些部分的乘积,最后再将结果合并,从而显著减少计算难度和步骤。这种策略可以有效降低大整数乘法所需的计算资源,并提高运算效率。

分治乘法的实现方式有多种,例如对称拆分策略、抽象模型等。此外,还可以结合其他算法技术,如加减运算技术、多位乘运算技术、快速傅里叶变换等,来进一步提升分治乘法的计算速度。

总的来说,分治乘法是一种高效解决大整数乘法问题的算法策略,它通过分解问题、分别求解子问题并合并结果的方式,显著降低了计算复杂度和资源消耗

常数优化介绍:

常数优化是编程中一种常见的优化手段,它主要是通过改进算法或代码实现,使得算法的运行时间减少,但其时间复杂度不变 。这种优化可以在不改变算法基本结构的情况下,显著提高程序的性能

常数优化的方法多种多样,包括但不限于以下几种:

  1. 读入优化:对于输入输出的优化,比如使用快读(fast read)代替标准的输入方式,可以显著提高程序的读入速度。
  2. 寄存器优化 :在循环中使用register关键字声明变量,这可以使得变量存储在寄存器中,而非内存中,从而加快访问速度。
  3. 内联函数优化 :使用inline关键字将短小的函数定义为内联函数,可以减少函数调用的开销。
  4. 判断语句优化 :三目运算符通常比if-else语句更快,因此可以适当地使用三目运算符来优化判断语句。
  5. 赋值优化 :对于多组询问的题目,如果数据是静态的,那么无需在每次询问前都进行清空操作。此外,当需要赋值的数组较大且元素值较为集中时,使用memset通常比使用for循环进行赋值更加高效。
  6. 类型优化 :选择合适的数据类型可以影响运算的速度。例如,long long类型下的运算通常比int类型慢一些,因此在不需要大整数运算的情况下,应优先使用int类型。
  7. 运算优化:使用位运算来替代一些复杂的算术运算,可以显著提高运算速度。特别是与2的次幂有关的操作,使用位运算通常更加高效。

此外,还有一些更为精致的常数优化方法,例如通过精妙地控制循环的边界或者搜索剪枝来减少不必要的计算,以及及时退出循环、简化布尔表达式、减少变量声明和拷贝等良好的编程习惯。

需要注意的是,虽然常数优化可以提高程序的性能,但过度优化可能会导致代码的可读性和可维护性下降。因此,在进行常数优化时,需要权衡性能提升和代码质量之间的关系。

这串代码是普通高精度题目的解法思路:

cpp 复制代码
//试过了,这道题通过简单的高精度算不出来,会时间超限。
//下列代码是简单的普通解法,过不了。
#include<stdio.h>  
#include<string.h>  
  
// 定义两个字符串s1和s2来存储输入的大整数  
char s1[1005], s2[1005];  
// 定义三个整型数组a, b, c来存储处理过程中的中间结果  
int a[1005], b[1005], c[1005];  
  
int main()  
{  
    // 初始化数组a, b, c为0  
    memset(a, 0, sizeof(a));  
    memset(b, 0, sizeof(b));  
    memset(c, 0, sizeof(c));  
  
    // 读取输入的两个大整数  
    scanf("%s%s", s1, s2);  
  
    // 获取两个大整数的长度  
    int len1 = strlen(s1);  
    int len2 = strlen(s2);  
  
    // 将s1逆序存储到数组a中,转换为整型数组  
    for (int i = 0, j = len1 - 1; i < len1; i++, j--) {  
        a[i] = s1[j] - '0';  
    }  
  
    // 将s2逆序存储到数组b中,转换为整型数组  
    for (int i = 0, j = len2 - 1; i < len2; i++, j--) {  
        b[i] = s2[j] - '0';  
    }  
  
    // 执行大整数乘法运算  
    for (int i = 0; i < len1; i++) {  
        for (int j = 0; j < len2; j++) {  
            // 计算乘积,并累加到数组c的对应位置  
            c[i + j] += a[i] * b[j];  
            // 处理进位  
            c[i + j + 1] += c[i + j] / 10;  
            c[i + j] %= 10;  
        }  
    }  
  
    // 找到数组c中的最高位非零元素的位置  
    int i = 1004;  
    while (i > 0 && c[i] == 0) {  
        i--;  
    }  
  
    // 输出最终结果  
    for (int j = i; j >= 0; j--) {  
        printf("%d", c[j]);  
    }  
  
    return 0;  
}

除法(A/B Problem)

题目描述

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

输入格式

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

输出格式

一行,商的整数部分。

输入输出样例

输入 #1复制

复制代码
10
2

输出 #1复制

复制代码
5

说明/提示

0≤a≤105000,1≤b≤109。

代码加解析:

cpp 复制代码
#include<stdio.h>  
#include<string.h>  
  
// 定义全局变量  
int b = 0, a[100000], c[1000000];  
char s1[100000];  
  
int main() {  
    // 初始化数组a和c为0  
    memset(a, 0, sizeof(a));  
    memset(c, 0, sizeof(c));  
      
    // 从标准输入读取字符串s1  
    scanf("%s", s1);  
      
    // 从标准输入读取整数b  
    scanf("%d", &b);  
      
    // 计算字符串s1的长度  
    int len = strlen(s1);  
      
    // 将字符串s1中的字符转换为数字并存储到数组a中  
    for (int i = 0; i < len; i++) {  
        a[i] = s1[i] - '0';  
    }  
      
    // 用于存储中间结果的变量  
    long long ans = 0;  
      
    // 计算除法结果并存储到数组c中  
    for (int i = 0; i < len; i++) {  
        c[i] = (ans * 10 + a[i]) / b;  
        ans = (ans * 10 + a[i]) % b;  
    }  
      
    // 查找商数组c中第一个非零元素的索引  
    int i = 0;  
    while (i < len && c[i] == 0) {  
        i++;  
    }  
      
    // 输出商  
    if (i < len) { // 如果找到了非零元素  
        for (int j = i; j < len; j++) { // 从第一个非零元素开始输出,直到商的最后一位  
            printf("%d", c[j]);  
        }  
    } else {  
        // 如果整个商都是零  
        printf("0"); // 输出0  
    }  
      
    return 0;  
}
相关推荐
阿阳微客3 小时前
Steam 搬砖项目深度拆解:从抵触到真香的转型之路
前端·笔记·学习·游戏
Chef_Chen8 小时前
从0开始学习R语言--Day18--分类变量关联性检验
学习
键盘敲没电8 小时前
【IOS】GCD学习
学习·ios·objective-c·xcode
海的诗篇_9 小时前
前端开发面试题总结-JavaScript篇(一)
开发语言·前端·javascript·学习·面试
AgilityBaby9 小时前
UE5 2D角色PaperZD插件动画状态机学习笔记
笔记·学习·ue5
AgilityBaby9 小时前
UE5 创建2D角色帧动画学习笔记
笔记·学习·ue5
武昌库里写JAVA10 小时前
iview Switch Tabs TabPane 使用提示Maximum call stack size exceeded堆栈溢出
java·开发语言·spring boot·学习·课程设计
一弓虽11 小时前
git 学习
git·学习
Moonnnn.13 小时前
【单片机期末】串行口循环缓冲区发送
笔记·单片机·嵌入式硬件·学习
viperrrrrrrrrr714 小时前
大数据学习(131)-Hive数据分析函数总结
大数据·hive·学习