当前位置: 首页 > news >正文

算法基础篇:(二)基础算法之高精度:突破数据极限

目录

前言

一、高精度算法的本质与核心思想

1.1 什么是高精度算法?

1.2 高精度算法的核心要素

1.3 高精度算法的适用场景

1.4 高精度运算的通用预处理步骤

二、高精度加法(High-Precision Addition)

2.1 算法原理

2.2 实现步骤

2.3 完整代码实现(ACM 模式)

2.4 代码解析与测试

代码解析

测试用例

2.5 易错点分析

三、高精度减法(High-Precision Subtraction)

3.1 算法原理

3.2 实现步骤

3.3 关键辅助函数:比较两个大整数的大小

3.4 完整代码实现(ACM 模式)

3.5 代码解析与测试

代码解析

测试用例

3.6 易错点分析

四、高精度乘法(High-Precision Multiplication)

4.1 算法原理

4.2 实现步骤

4.3 完整代码实现(ACM 模式)

4.4 代码解析与测试

代码解析

测试用例

4.5 易错点分析

五、高精度除法(High-Precision Division)

5.1 算法原理

5.2 实现步骤(整除 + 取模)

5.3 完整代码实现(ACM 模式,含整除和取模)

5.4 代码解析与测试

代码解析

测试用例

5.5 易错点分析

六、高精度算法的优化技巧

6.1 存储优化:向量 vs 数组

6.2 运算优化:减少冗余操作

6.3 性能优化:处理超大长度数据

6.4 代码复用:封装成工具类

七、实战练习与拓展

7.1 经典练习题(洛谷)

7.2 练习建议

总结


前言

        在做算法题的时候,我们经常会遇到需要处理极大或极小数值的场景 —— 比如计算两个 1000 位的整数相乘、求 100! 的精确结果,或是处理天文数字级别的数据。此时,C++ 内置的int(最大约 2×10⁹)、long long(最大约 9×10¹⁸)等基础数据类型早已无法满足需求,它们会因数值溢出而导致结果错误。

        为了解决这一问题,高精度算法应运而生。高精度算法的核心思想是 “模拟人类列竖式运算的过程”,通过字符串或数组存储超大数值的每一位,再按照四则运算的规则逐位处理,从而实现任意长度数值的精确计算。

        本文将从高精度算法的本质出发,详细讲解高精度加法、减法、乘法、除法(含整除与取模)的原理、实现步骤、代码细节及优化技巧。全文采用 C++ 实现,兼容 ACM 竞赛提交格式,同时包含大量实例、易错点分析和实战练习建议,无论你是编程新手还是竞赛选手,都能彻底掌握高精度运算。


一、高精度算法的本质与核心思想

1.1 什么是高精度算法?

        高精度算法(High-Precision Algorithm),又称 “大整数运算”,是指处理超出标准数据类型表示范围的数值运算的算法。它不依赖硬件提供的数值存储能力,而是通过软件模拟的方式,将超大数值拆分为单个数字(0-9)进行存储和运算,最终得到精确结果。

        例如,计算 99999999999999999999 + 1 时:

  • 基础数据类型:long long 无法存储该数值,直接运算会溢出;
  • 高精度算法:将数值存储为字符串 "99999999999999999999",模拟列竖式加法,从右往左逐位相加,处理进位,最终得到结果 "100000000000000000000"

1.2 高精度算法的核心要素

        要实现可靠的高精度运算,必须把握以下 4 个核心要素:

  1. 存储方式:选择合适的结构存储每一位数字(数组 / 向量为最优选择);
  2. 位序处理:通常采用 “逆序存储”(低位在前、高位在后),便于从右往左逐位运算;
  3. 进位 / 借位处理:严格遵循竖式运算规则,处理加法进位、减法借位、乘法进位、除法余数;
  4. 结果整理:去掉结果中的前导零,处理负数符号,按正确位序输出。

1.3 高精度算法的适用场景

  • 编程竞赛:NOIP、蓝桥杯等竞赛中的大整数运算题(如高精度加法、乘法、阶乘计算);
  • 工程实践:密码学(大质数运算)、金融计算(高精度货币计算)、科学计算(天文 / 物理数据)等;
  • 算法学习:巩固数组操作、循环控制、逻辑思维,为复杂算法(如高精度动态规划)铺垫基础。

1.4 高精度运算的通用预处理步骤

        无论哪种高精度运算,都需要先完成以下预处理工作,这是保证代码简洁、高效的关键:

  1. 读取输入:用字符串读取超大数值(避免溢出);
  2. 逆序存储:将字符串转换为数组 / 向量,且低位在前、高位在后(例如 "1234" 存储为 [4,3,2,1]);
  3. 初始化结果容器:创建存储结果的数组 / 向量,初始值为 0;
  4. 处理符号:分离数值的正负号(减法 / 除法需特殊处理)。

        为什么要逆序存储?

  • 人类在列竖式运算时,是从最低位(最右边)开始逐位计算的,逆序存储后,数组下标 0 对应最低位,下标 1 对应次低位,以此类推,可通过循环从 0 开始逐位处理,无需反向遍历字符串;
  • 进位 / 借位是从低位向高位传递,逆序存储有助于直接在数组尾部扩展高位(如加法最高位有进位时,直接在数组末尾添加 1)。

二、高精度加法(High-Precision Addition)

2.1 算法原理

        高精度加法的核心是模拟人类列竖式加法,规则如下:

  1. 两个数的最低位对齐
  2. 从最低位开始,逐位相加(包含上一位的进位);
  3. 当前位结果 =(加数 1 当前位 + 加数 2 当前位 + 进位)% 10
  4. 新的进位 =(加数 1 当前位 + 加数 2 当前位 + 进位)/ 10
  5. 遍历完所有位后,若进位不为 0,需将进位添加到结果的最高位。

        示例:计算 1234 + 56789

  • 逆序存储后:a = [4,3,2,1](1234),b = [9,8,7,6,5](56789);
  • 逐位计算:
    • 位 0:4+9+0=13 → 结果位 0=3,进位 = 1;
    • 位 1:3+8+1=12 → 结果位 1=2,进位 = 1;
    • 位 2:2+7+1=10 → 结果位 2=0,进位 = 1;
    • 位 3:1+6+1=8 → 结果位 3=8,进位 = 0;
    • 位 4:0+5+0=5 → 结果位 4=5,进位 = 0;
  • 结果逆序后:[5,8,0,2,3] → 正序为 58023,与实际结果一致。

2.2 实现步骤

  1. 读取两个大整数的字符串 s1 和 s2
  2. 将字符串逆序转换为向量 a 和 b(低位在前);
  3. 初始化进位 carry = 0,结果向量 res
  4. 循环遍历 a 和 b 的每一位(遍历长度为两者的最大值):
    • 取 a[i](若 i 超出 a 的长度,取 0);
    •  b[i](若 i 超出 b 的长度,取 0);
    • 计算当前位总和 sum = a[i] + b[i] + carry
    • 结果位 sum % 10 存入 res
    • 更新进位 carry = sum / 10
  5. 遍历结束后,若 carry > 0,将 carry 存入 res
  6. 将 res 逆序输出(去掉前导零,此处加法无负号)。

2.3 完整代码实现(ACM 模式)

#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
using namespace std;// 高精度加法:a + b,a和b均为非负整数的字符串形式
string add(string s1, string s2) {// 逆序存储到向量(低位在前)vector<int> a, b, res;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');int carry = 0;  // 进位int len = max(a.size(), b.size());for (int i = 0; i < len; ++i) {// 取当前位的值,超出长度则为0int x = i < a.size() ? a[i] : 0;int y = i < b.size() ? b[i] : 0;int sum = x + y + carry;res.push_back(sum % 10);  // 当前位结果carry = sum / 10;         // 更新进位}// 处理最高位的进位if (carry != 0) res.push_back(carry);// 转换为字符串(逆序输出)string ans;for (int i = res.size() - 1; i >= 0; --i) {ans += (res[i] + '0');}return ans;
}int main() {string s1, s2;cin >> s1 >> s2;cout << add(s1, s2) << endl;return 0;
}

2.4 代码解析与测试

代码解析
  • 存储转换:通过循环将字符串从尾到头遍历,转换为向量,实现逆序存储;
  • 逐位计算:用 max(a.size(), b.size()) 确保遍历完所有有效位,不足的位用 0 补齐;
  • 进位处理:每次计算后更新进位,遍历结束后若进位不为 0,需添加到结果末尾(最高位);
  • 结果转换:将结果向量逆序遍历,转换为字符串输出,保证正序显示。
测试用例
  • 测试用例 1(普通情况):输入:1234 56789 → 输出:58023
  • 测试用例 2(有进位):输入:9999 1 → 输出:10000
  • 测试用例 3(长度不一致):输入:123456789 987654321 → 输出:1111111110
  • 测试用例 4(含零):输入:0 12345 → 输出:12345

2.5 易错点分析

  1. 忘记处理最高位进位:如 999 + 1,若未添加进位,结果会是 000 而非 1000
  2. 字符串转向量时顺序错误:若正序存储(高位在前),会导致遍历顺序与竖式运算相反,结果错误;
  3. 未处理长度不一致:如 123 + 4567,若只遍历到较短的长度,会丢失较长数的高位;
  4. 零的处理:输入为 0 时,转换后向量为 [0],输出时需正常显示,不能省略。

三、高精度减法(High-Precision Subtraction)

3.1 算法原理

        高精度减法的核心是模拟人类列竖式减法,规则如下(假设被减数 ≥ 减数,结果非负):

  1. 两个数的最低位对齐
  2. 从最低位开始,逐位相减(若被减数当前位 < 减数当前位,需向高位借位);
  3. 当前位结果 =(被减数当前位 - 减数当前位 + 借位补偿)% 10
  4. 借位标记:若被减数当前位 < 减数当前位,借位为 1(下一位需减 1),否则为 0;
  5. 遍历完所有位后,去掉结果中的前导零(如结果为 000123,需改为 123)。

特殊处理:若被减数 < 减数,结果为负数,需交换被减数和减数,计算绝对值后添加负号。

示例1:计算 56789 - 1234

  • 逆序存储后:a = [9,8,7,6,5](56789),b = [4,3,2,1](1234);
  • 逐位计算:
    • 位 0:9-4=5 → 结果位 0=5,借位 = 0;
    • 位 1:8-3=5 → 结果位 1=5,借位 = 0;
    • 位 2:7-2=5 → 结果位 2=5,借位 = 0;
    • 位 3:6-1=5 → 结果位 3=5,借位 = 0;
    • 位 4:5-0=5 → 结果位 4=5,借位 = 0;
  • 结果逆序后:[5,5,5,5,5] → 正序为 55555,与实际结果一致。

示例2(含借位):计算 10000 - 1

  • 逆序存储后:a = [0,0,0,0,1](10000),b = [1](1);
  • 逐位计算:
    • 位 0:0-1 < 0 → 借位 1,补偿 10 → 10-1=9 → 结果位 0=9,借位 = 1;
    • 位 1:0-0-1 < 0 → 借位 1,补偿 10 → 10-0-1=9 → 结果位 1=9,借位 = 1;
    • 位 2:0-0-1 < 0 → 借位 1,补偿 10 → 10-0-1=9 → 结果位 2=9,借位 = 1;
    • 位 3:0-0-1 < 0 → 借位 1,补偿 10 → 10-0-1=9 → 结果位 3=9,借位 = 1;
    • 位 4:1-0-1=0 → 结果位 4=0,借位 = 0;
  • 结果逆序后:[0,9,9,9,9] → 去掉前导零后为 9999,与实际结果一致。

3.2 实现步骤

  1. 读取两个大整数的字符串 s1(被减数)和 s2(减数);
  2. 判断 s1 和 s2 的大小:
    • 若 s1 < s2:结果为负数,交换 s1 和 s2,标记负号;
    • 若 s1 == s2:直接返回 0
  3. 将字符串逆序转换为向量 a 和 b(低位在前);
  4. 初始化借位 borrow = 0,结果向量 res
  5. 循环遍历 a 的每一位(a 长度 ≥ b 长度):
    • 取 a[i](必存在);
    • 取 b[i](若 i 超出 b 的长度,取 0);
    • 计算当前位差值 diff = a[i] - b[i] - borrow
    • 若 diff < 0diff += 10borrow = 1(借位);
    • 若 diff ≥ 0borrow = 0
    • 将 diff 存入 res
  6. 去掉 res 中的前导零(从末尾开始删除连续的 0,至少保留 1 个 0);
  7. 若标记了负号,在结果前添加 -,否则直接逆序输出 res

3.3 关键辅助函数:比较两个大整数的大小

        由于 s1 和 s2 是字符串形式的大整数,无法直接用 < 或 > 比较,需实现专门的比较函数:

// 比较两个非负大整数字符串的大小:s1 < s2 返回true,否则返回false
bool isLess(string s1, string s2) {if (s1.size() != s2.size()) {// 长度不同:长度短的数更小return s1.size() < s2.size();} else {// 长度相同:逐位比较,从高位到低位return s1 < s2;}
}

3.4 完整代码实现(ACM 模式)

#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
using namespace std;// 比较两个非负大整数字符串的大小:s1 < s2 返回true,否则返回false
bool isLess(string s1, string s2) {if (s1.size() != s2.size()) {return s1.size() < s2.size();} else {return s1 < s2;}
}// 高精度减法:s1 - s2,返回结果字符串(处理负数和零)
string subtract(string s1, string s2) {// 处理特殊情况:s1 == s2if (s1 == s2) return "0";bool isNegative = false;// 若s1 < s2,交换两者,标记结果为负数if (isLess(s1, s2)) {swap(s1, s2);isNegative = true;}// 逆序存储到向量(低位在前)vector<int> a, b, res;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');int borrow = 0;  // 借位for (int i = 0; i < a.size(); ++i) {int x = a[i];int y = i < b.size() ? b[i] : 0;int diff = x - y - borrow;if (diff < 0) {diff += 10;borrow = 1;} else {borrow = 0;}res.push_back(diff);}// 去掉前导零(从末尾开始删除,保留至少一个零)while (res.size() > 1 && res.back() == 0) {res.pop_back();}// 转换为字符串(逆序输出)string ans;if (isNegative) ans += '-';  // 添加负号for (int i = res.size() - 1; i >= 0; --i) {ans += (res[i] + '0');}return ans;
}int main() {string s1, s2;cin >> s1 >> s2;cout << subtract(s1, s2) << endl;return 0;
}

3.5 代码解析与测试

代码解析
  • 大小比较isLess 函数先比较长度,再逐位比较,确保正确判断两个大整数的大小;
  • 负号处理:通过 isNegative 标记结果符号,交换被减数和减数后计算绝对值;
  • 借位处理:若当前位差值为负,添加 10 补偿,同时设置借位为 1,下一位计算时需减去借位;
  • 前导零处理:从结果向量末尾删除连续的 0(因为逆序存储,末尾对应高位),避免输出 00123 这类结果。
测试用例
  • 测试用例 1(普通情况):输入:56789 1234 → 输出:55555
  • 测试用例 2(含借位):输入:10000 1 → 输出:9999
  • 测试用例 3(被减数小于减数):输入:1234 56789 → 输出:-55555
  • 测试用例 4(含零):输入:12345 0 → 输出:12345
  • 测试用例 5(前导零):输入:1000 999 → 输出:1

3.6 易错点分析

  1. 未处理借位传递:如 1000 - 1,若借位未传递到所有低位,会导致结果错误(如 999 而非 999,实际上正确,但还是需要注意多借位情况);
  2. 前导零未删除:如 1230 - 1200,结果向量为 [0,3,0,0],逆序后为 0030,需删除前导零变为 30
  3. 负号标记错误:交换被减数和减数后,忘记标记负号,导致结果符号错误;
  4. 相等情况未处理:如 1234 - 1234,未直接返回 0,会导致结果为 0000,虽然后续删除前导零后正确,但还是需要优化逻辑。

四、高精度乘法(High-Precision Multiplication)

4.1 算法原理

        高精度乘法的核心是模拟人类列竖式乘法,规则如下(假设乘数为非负整数):

  1. 两个数的最低位对齐
  2. 用乘数的每一位分别与被乘数的每一位相乘,结果的最低位对应当前两位的下标之和(如被乘数第 i 位 × 乘数第 j 位,结果最低位在第 i+j 位);
  3. 逐位累加所有乘积结果,同时处理进位;
  4. 遍历完所有位后,去掉结果中的前导零。

关键结论:若被乘数长度为 len1,乘数长度为 len2,则乘积的最大长度为 len1 + len2(如 999 × 999 = 998001,长度 3+3=6)。

示例:计算 123 × 45

  • 逆序存储后:a = [3,2,1](123),b = [5,4](45);
  • 逐位相乘并累加:
    • 乘数第 0 位(5)× 被乘数第 0 位(3)= 15 → 结果第 0 位 = 15;
    • 乘数第 0 位(5)× 被乘数第 1 位(2)= 10 → 结果第 1 位 = 10;
    • 乘数第 0 位(5)× 被乘数第 2 位(1)= 5 → 结果第 2 位 = 5;
    • 乘数第 1 位(4)× 被乘数第 0 位(3)= 12 → 结果第 1 位 = 10+12=22;
    • 乘数第 1 位(4)× 被乘数第 1 位(2)= 8 → 结果第 2 位 = 5+8=13;
    • 乘数第 1 位(4)× 被乘数第 2 位(1)= 4 → 结果第 3 位 = 4;
  • 处理进位:
    • 位 0:15 → 结果位 0=5,进位 1;
    • 位 1:22 + 1=23 → 结果位 1=3,进位 2;
    • 位 2:13 + 2=15 → 结果位 2=5,进位 1;
    • 位 3:4 + 1=5 → 结果位 3=5,进位 0;
  • 结果逆序后:[5,5,3,5] → 正序为 5535,与实际结果一致(123×45=5535)。

4.2 实现步骤

  1. 读取两个大整数的字符串 s1(被乘数)和 s2(乘数);
  2. 处理特殊情况:若其中一个数为 0,直接返回 0
  3. 将字符串逆序转换为向量 a 和 b(低位在前);
  4. 初始化结果向量 res(长度为 a.size() + b.size(),初始值为 0);
  5. 双重循环逐位相乘并累加
    • 外层循环遍历乘数 b 的每一位 j
    • 内层循环遍历被乘数 a 的每一位 i
    • res[i + j] += a[i] * b[j](累加乘积到对应位置);
  6. 处理进位
    • 遍历 res 的每一位(从 0 到 res.size()-1);
    • 当前位进位 = res[i] / 10
    • 当前位结果 = res[i] % 10
    • 下一位累加进位(res[i+1] += 进位);
  7. 去掉 res 中的前导零(从末尾开始删除连续的 0,至少保留 1 个 0);
  8. 将 res 逆序输出,得到最终结果。

4.3 完整代码实现(ACM 模式)

#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
using namespace std;// 高精度乘法:s1 × s2,s1和s2均为非负整数的字符串形式
string multiply(string s1, string s2) {// 处理特殊情况:有一个数为0if (s1 == "0" || s2 == "0") return "0";// 逆序存储到向量(低位在前)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');int len1 = a.size(), len2 = b.size();vector<int> res(len1 + len2, 0);  // 结果最大长度为len1+len2// 逐位相乘并累加for (int j = 0; j < len2; ++j) {  // 遍历乘数的每一位for (int i = 0; i < len1; ++i) {  // 遍历被乘数的每一位res[i + j] += a[i] * b[j];}}// 处理进位int carry = 0;for (int i = 0; i < res.size(); ++i) {res[i] += carry;carry = res[i] / 10;res[i] %= 10;}// 去掉前导零(从末尾开始删除)while (res.size() > 1 && res.back() == 0) {res.pop_back();}// 转换为字符串(逆序输出)string ans;for (int i = res.size() - 1; i >= 0; --i) {ans += (res[i] + '0');}return ans;
}int main() {string s1, s2;cin >> s1 >> s2;cout << multiply(s1, s2) << endl;return 0;
}

4.4 代码解析与测试

代码解析
  • 特殊情况处理:若任一输入为 0,直接返回 0,避免无效计算;
  • 结果初始化:结果向量长度设为 len1 + len2,确保有足够空间存储所有乘积位;
  • 双重循环相乘:外层遍历乘数,内层遍历被乘数,乘积结果存入 res[i+j],符合竖式乘法的位对齐规则;
  • 进位处理:单独遍历结果向量处理进位,逻辑更清晰,能够避免边乘边处理导致的混乱;
  • 前导零处理:与减法一致,从末尾删除连续的 0,确保结果格式正确。
测试用例
  • 测试用例 1(普通情况):输入:123 45 → 输出:5535
  • 测试用例 2(大数相乘):输入:999999999 999999999 → 输出:999999998000000001
  • 测试用例 3(含零):输入:1234 0 → 输出:0
  • 测试用例 4(长度不一致):输入:12345 678 → 输出:8370910
  • 测试用例 5(单个数字):输入:9 9 → 输出:81

4.5 易错点分析

  1. 结果向量长度不足:若未设置为 len1 + len2,可能导致高位乘积溢出向量,结果错误;
  2. 双重循环顺序错误:若外层遍历被乘数、内层遍历乘数,逻辑上也是正确的,但需注意 i 和 j 的对应关系,避免位对齐错误;
  3. 进位处理顺序错误:需先累加进位再计算当前位结果,否则会遗漏上一位的进位;
  4. 前导零未删除:如 100 × 123 = 12300,结果向量逆序后为 00321,删除前导零后为 12300,若未删除则输出 0032
  5. 忘记处理进位:如 999 × 999,若未处理进位,结果会是 81 81 81(未累加进位),而非 998001

五、高精度除法(High-Precision Division)

5.1 算法原理

        高精度除法分为高精度除以低精度(除数为普通整数,如 12345 ÷ 7)和高精度除以高精度(除数为大整数,如 123456789 ÷ 9876),本文重点讲解更常用的高精度除以低精度

        高精度除以低精度的核心是模拟人类列竖式除法,规则如下(假设被除数为非负整数,除数为正整数):

  1. 从被除数的最高位开始,依次取前 k 位组成一个临时数(确保临时数 ≥ 除数);
  2. 计算临时数 ÷ 除数的商,作为结果的当前位;
  3. 计算临时数 % 除数的余数,作为下一次计算的初始临时数;
  4. 重复步骤 1-3,直到遍历完被除数的所有位;
  5. 去掉结果中的前导零,余数保留(若需要)。

示例:计算 12345 ÷ 7(商为 1763,余数为 4)

  • 被除数正序存储(高位在前):a = [1,2,3,4,5](12345);
  • 逐位计算:
    • 取第 0 位(1):1 < 7 → 临时数 = 1,商当前位补 0(后续删除前导零);
    • 取第 1 位(2):临时数 = 1×10 + 2=12 → 12 ÷7=1(商位 0=1),余数 = 12%7=5;
    • 取第 2 位(3):临时数 = 5×10 +3=53 → 53÷7=7(商位 1=7),余数 = 53%7=4;
    • 取第 3 位(4):临时数 = 4×10 +4=44 →44÷7=6(商位 2=6),余数 = 44%7=2;
    • 取第 4 位(5):临时数 = 2×10 +5=25 →25÷7=3(商位 3=3),余数 = 25%7=4;
  • 结果商为 [1,7,6,3](正序),余数为 4,与实际结果一致。

5.2 实现步骤(整除 + 取模)

  1. 读取高精度被除数字符串 s 和低精度除数 dd > 0);
  2. 处理特殊情况:
    •  s == "0":商为 0,余数为 0
    • 若 d == 1:商为 s,余数为 0
  3. 将被除数字符串正序转换为向量 a(高位在前,便于从高位开始取数);
  4. 初始化临时数 temp = 0,商向量 res,余数 remainder = 0
  5. 循环遍历 a 的每一位(从高位到低位):
    • temp = temp * 10 + a[i](更新临时数);
    • 若 temp >= d
      • 商位 = temp / d,存入 res
      • temp = temp % d(更新余数);
    • 若 temp < d 且 res 不为空(避免前导零):
      • 商位补 0,存入 res
  6. 处理商的前导零:若 res 为空(被除数 < 除数),商为 0;否则删除开头连续的 0
  7. 余数 remainder = temp
  8. 将商向量转换为字符串输出,余数按需输出。

5.3 完整代码实现(ACM 模式,含整除和取模)

#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
using namespace std;// 高精度除以低精度:s ÷ d,返回 pair<商, 余数>,d为正整数
pair<string, int> divide(string s, int d) {vector<int> a;for (char c : s) a.push_back(c - '0');  // 正序存储(高位在前)vector<int> res;int temp = 0;  // 临时数,存储当前待除的部分for (int i = 0; i < a.size(); ++i) {temp = temp * 10 + a[i];  // 更新临时数if (temp >= d) {// 计算当前商位res.push_back(temp / d);// 更新临时数为余数temp = temp % d;} else {// 临时数小于除数,若商已开始(res非空),补0if (!res.empty()) {res.push_back(0);}}}// 处理商的前导零string quotient;if (res.empty()) {// 被除数 < 除数,商为0quotient = "0";} else {// 删除开头的连续零int start = 0;while (start < res.size() && res[start] == 0) start++;if (start == res.size()) {quotient = "0";} else {for (int i = start; i < res.size(); ++i) {quotient += (res[i] + '0');}}}int remainder = temp;  // 最终余数return {quotient, remainder};
}int main() {string s;int d;cin >> s >> d;auto [quotient, remainder] = divide(s, d);cout << "商:" << quotient << endl;cout << "余数:" << remainder << endl;return 0;
}

5.4 代码解析与测试

代码解析
  • 存储方式:被除数采用正序存储(高位在前),符合竖式除法从高位开始计算的习惯;
  • 临时数处理temp 存储当前待除的部分,每次更新为 temp*10 + a[i],模拟人类逐位取数的过程;
  • 商位补零:当临时数小于除数但商已开始(res 非空)时,补 0,避免商为 1763 变成 173(中间位漏 0);
  • 前导零处理:若商向量为空(被除数 < 除数),商为 0;否则删除开头的连续零,确保格式正确;
  • 余数返回:最终 temp 即为余数,满足题目对取模的需求。
测试用例
  • 测试用例 1(普通情况):输入:12345 7 → 输出:商:1763,余数:4
  • 测试用例 2(被除数 < 除数):输入:123 456 → 输出:商:0,余数:123
  • 测试用例 3(整除):输入:10000 25 → 输出:商:400,余数:0
  • 测试用例 4(含零):输入:0 123 → 输出:商:0,余数:0
  • 测试用例 5(大数除法):输入:999999999999999999 9 → 输出:商:111111111111111111,余数:0

5.5 易错点分析

  1. 存储方式错误:若被除数逆序存储(低位在前),会导致从低位开始取数,与竖式除法逻辑相反,结果错误;
  2. 商位补零遗漏:如 1000 ÷ 25,计算过程中临时数可能出现小于除数的情况,若未补零,商可能为 4 而非 40
  3. 前导零未处理:如 1234 ÷ 10,商向量为 [1,2,3,4](实际应为 123,余数 4),需删除前导零?不,1234 ÷10 商为 123,余数 4,代码中 temp 初始为 0,遍历 a = [1,2,3,4]
    • i=0:temp=0×10+1=1 <10 → res 为空,不补零;
    • i=1:temp=1×10+2=12 ≥10 → 商位 1,temp=2 → res=[1];
    • i=2:temp=2×10+3=23 ≥10 → 商位 2,temp=3 → res=[1,2];
    • i=3:temp=3×10+4=34 ≥10 → 商位 3,temp=4 → res=[1,2,3];商为 123,正确,无零;
  4. 除数为零未处理:代码中假设除数 d > 0,实际应用中需添加除数为零的异常处理;
  5. 临时数溢出:由于除数是低精度整数(int 范围),temp 的最大值为 d-1(每次取模后),是不会溢出的,无需担心。

六、高精度算法的优化技巧

6.1 存储优化:向量 vs 数组

  • 数组:需预先定义最大长度(如 const int N = 1e6),适合已知数据规模的场景,访问速度快;
  • 向量(vector:动态扩容,无需预先定义长度,适合数据规模未知的场景,代码更灵活。

        推荐使用向量:竞赛中数据规模往往不固定,向量的动态扩容特性可避免数组长度不足或浪费空间的问题。

6.2 运算优化:减少冗余操作

  1. 提前处理特殊情况:如加法中的 0、乘法中的 0 和 1、除法中的 0 和 1,直接返回结果,避免无效计算;
  2. 合并循环:如乘法中,可将 “逐位相乘” 和 “进位处理” 合并为一个循环,但需注意逻辑清晰,避免出错;
  3. 减少类型转换:字符串转向量时,一次性完成转换,避免循环中重复转换。

6.3 性能优化:处理超大长度数据

        当数值长度达到 1e5 位时,高精度运算的时间复杂度会显著增加(加法 / 减法:O (n),乘法:O (nm),除法:O (n)),可以通过以下的方式进行优化:

  1. 分块存储:将每几位数字(如 4 位)存储为一个整数(如 int 可存储 0-9999),减少循环次数(如 1e5 位数字分块后为 2.5e4 个块);
  2. 快速傅里叶变换(FFT):将乘法的时间复杂度从 O (nm) 优化到 O (n log n),适合超大规模(如 1e5 位)的乘法运算;
  3. 使用更快的输入输出:竞赛中用 scanf/printf 替代 cin/cout,或关闭同步(ios::sync_with_stdio(false); cin.tie(0);),减少输入输出耗时。

6.4 代码复用:封装成工具类

        在竞赛或工程实践中,可将高精度运算封装成工具类,便于复用:

class HighPrecision {
public:// 加法static string add(string s1, string s2) { ... }// 减法static string subtract(string s1, string s2) { ... }// 乘法static string multiply(string s1, string s2) { ... }// 除法(高精度÷低精度)static pair<string, int> divide(string s, int d) { ... }// 比较大小static bool isLess(string s1, string s2) { ... }
};

七、实战练习与拓展

7.1 经典练习题(洛谷)

  1. 高精度加法:P1601 A+B Problem(高精)https://www.luogu.com.cn/problem/P1601
  2. 高精度减法:P2142 高精度减法  https://www.luogu.com.cn/problem/P2142
  3. 高精度乘法:P1303 A*B Problem  https://www.luogu.com.cn/problem/P1303
  4. 高精度除法:P1480 A/B Problem  https://www.luogu.com.cn/problem/P1480

7.2 练习建议

  1. 先掌握基础运算(加、减、乘、除),再尝试综合题(如阶乘之和、大整数幂等);
  2. 短长度数据开始测试,逐步过渡到超长数据(如 1e4 位),验证代码的稳定性;
  3. 手动模拟运算过程,对比代码输出结果,找出逻辑错误;
  4. 阅读优秀代码,学习优化技巧(如分块存储、FFT 优化等)。

总结

        高精度算法看似复杂,但只要抓住 “模拟竖式” 这一核心,拆分步骤、逐步实现,就能写出稳定可靠的代码。它不仅是编程竞赛的必备技能,也是工程实践中处理超大数值的重要工具。

        建议在学习后多做练习,通过实际题目巩固知识点,同时尝试拓展负数运算、高精度除以高精度等进阶内容,进一步提升编程能力。

        如果本文对你有帮助,欢迎点赞、收藏、转发,也欢迎在评论区交流讨论~ 

http://www.dtcms.com/a/576916.html

相关文章:

  • 香港100G高防服务器的防御力如何?
  • 网站文章怎么做分享qq网站建设步骤详解视频教程
  • 开发者实践:机器人集群的 API 对接与 MQTT 边缘调度解耦
  • 百日挑战——单词篇(第十五天)
  • 中国SIP中继类型
  • Kubernetes 原生滚动更新(Rolling Update)完整实践指南
  • 沈阳做企业网站哪家好网架提升公司
  • [N_151]基于微信小程序校园学生活动管理平台
  • Stager贴花工作流:告别Painter的“烘焙式”贴图
  • Linux 开发语言选择指南:不同场景该用哪种?
  • h5网站动画怎么做的重庆企业网络推广价格
  • 免费创建网站带咨询的免费企业网站程序asp
  • css 宽度屏幕50%,高度等于宽度的50%,窗口变化,比例不变(宽度百分比,高度等比例自适应)
  • Photoshop通道的应用
  • VUE3+element-plus 循环列表中图标由后台动态添加
  • LangFlow前端源码深度解析:核心模块与关键实现
  • 从 Rust 到 Flutter:嵌入式图形与构建工具全景指南
  • 转折·融合·重构——2025十大新兴技术驱动系统变革与全球挑战应对
  • IP地址、子网掩码与网段:网络划分的核心概念
  • 怎样才能在百度搜索到自己的网站wordpress去掉分类栏目前缀
  • 视频孪生与空间智能:重构物理世界的时空认知范式
  • Rust 练习册 11 :可变变量与可变引用详解
  • 在VSCode中:解决终端输出中文乱码问题
  • MATLAB基于BNT工具箱的多输入分类预测
  • 【主流开发语言深度对比】Python/Go/Java/JS/Rust/C++评测
  • 从开发到部署
  • 【无标题】Vscode 报错 got bad result from install script无法远程链接服务器
  • 基于Linux的TCP服务端客户端通信(一)
  • 在 VSCode 中:引入开源cJSon解析库+示例demo
  • SwiftUI 组件开发: 自定义下拉刷新和加载更多(iOS 15 兼容)