高精度算法--四则运算模板(附例题)
高精度加法
A + B 适用范围:A和B的位数在10^6以内(注意是位数,不是数字大小),A、B均为非负数
模版中用到的函数:
size()函数:获取容器(字符串,vector数组)中的元素数量,返回容器中元素的个数,类型为size_t .
size_t 是无符号的,不能表示负数,在写循环时需要注意 i 的类型.
push_back()函数: 在容器末尾添加元素.
#include<iostream>
#include<vector>
using namespace std;// 大整数相加函数,A和B为逆序存储的数字数组(低位在前,高位在后)
vector<int> add(vector<int> &A, vector<int> &B)
{vector<int> C; // 存储结果的数组int t = 0; // 当前位的累加值(包含进位)for (size_t i = 0; i < A.size() || i < B.size() || t; i++) {// 将当前位的两个数字相加,并加上进位if (i < A.size()) t += A[i];if (i < B.size()) t += B[i];C.push_back(t % 10); // 取个位作为当前位的结果t /= 10; // 计算进位,用于下一位的累加}return C;
}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 C = add(A, B); // 结果数组为逆序存储,逆序输出即为正确的高位在前顺序for (int i = C.size() - 1; i >= 0; i--) printf("%d", C[i]);return 0;
}
高精度减法
A - B 适用范围:A和B的位数在10^6以内,A、B均为非负数
模版中用到的函数:
back()函数:返回容器vector的最后一个元素.
pop_back()函数:移除容器末尾的元素.
注意:减法运算先比较两数大小,模拟运算时需要处理低位向高位借一
#include<iostream>
#include<vector>
using namespace std;/* 比较两个大整数的大小
A > B 时返回true,否则返回false
比较规则:1.位数不同时位数多的大 2.位数相同时从高位开始逐位比较 */
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]; // 高位数字不同时比较当前位return true; // 所有位都相等时视为A>=B
}// 减法函数(A>=B)
vector<int> sub(vector<int> &A, vector<int> &B)
{vector<int> C; int t = 0; // 借位标志(0无借位,1有借位)for (size_t i = 0; i < A.size(); i++) {t = A[i] - t; // 减去上一位的借位if(i < B.size()) t -= B[i]; // 对应位相减// 处理借位:+10确保取到正数,%10取个位数C.push_back((t + 10) % 10); // 更新借位状态:t<0说明需要借位if(t < 0) t = 1; else t = 0;}// 去除前导零(保留至少1位)while(C.size() > 1 && C.back() == 0) C.pop_back();return C;
}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)){ // A>=Bauto C = sub(A, B); for (int i = C.size() - 1; i >= 0; i--) printf("%d", C[i]);}else{ // A < B,计算B-A并添加负号auto C = sub(B, A); printf("-"); for (int i = C.size() - 1; i >= 0; i--) printf("%d", C[i]);}return 0;
}
高精度乘法
A * B 适用范围:A的位数在10^6以内,B的值在10000以内(注意),A、B均为非负数.
如果A、B都是大数该如何解决呢?见例题1.
乘法模版相对简单易记
#include<iostream>
#include<vector>
using namespace std;
vector<int> mul(vector<int> &A, int b)
{vector<int> C; int t = 0; // 进位值(包含当前位的累计值)for (size_t i = 0; i < A.size() || t; i++) {// 当前位乘法计算(如果还有未处理的位)if(i < A.size()) t += A[i] * b; // 核心计算:当前位×b + 进位C.push_back(t % 10); // 取个位作为当前结果t /= 10; }// 处理前导零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--) printf("%d", C[i]);return 0;
}
高精度除法
A / B 适用范围:A的位数在10^6以内,B的值在10000以内(注意),A、B均为非负数.
商C,余数r(通过引用返回)
存储顺序的特殊处理说明:
在加减乘运算中,计算均从低位(个位)开始,结果自然以逆序存储在数组中(如结果123存储为[3,2,1]),主函数通过逆序输出即可得到正确的高位在前顺序。
但除法运算需从高位开始逐位计算,导致商值的每一位按正序生成(如商500初始存储为[5,0,0])。为保持与其他运算一致的存储规范,需对结果进行反转,使其转换为逆序存储([5,0,0] → [0,0,5])。此时主函数沿用统一的逆序输出逻辑,即可正确展示高位在前的最终结果(输出500)。
模版中用到的函数:
reverse()函数:反转容器(如 vector、string、数组等)中元素的顺序.
头文件需包含 <algorithm>.
begin()函数:返回指向容器第一个元素.
end()函数:返回指向容器最后一个元素下一个位置.
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
vector<int> div(vector<int> &A, int b, int &r)
{vector<int> C; r = 0; // 初始化余数为0// 从高位到低位逐位处理(原数字的最高位对应A的最后一个元素)for (int i = A.size() - 1; i >= 0; i--) {r = r * 10 + A[i]; // 将余数进位并加上当前位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 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 r; // 存储余数auto C = div(A, b, r); // 逆序输出商(高位在前)for (int i = C.size() - 1; i >= 0; i--) printf("%d", C[i]);cout << endl << r << endl; // 输出余数return 0;
}
自此,四则运算模版已解决,下面带入到具体题目中使用.
例题1 (洛谷P1303 A*B Problem)
题目背景
高精度乘法模板题。
题目描述
给出两个非负整数,求它们的乘积。
输入格式
输入共两行,每行一个非负整数。
输出格式
输出一个非负整数表示乘积。
说明/提示
每个非负整数不超过 10^2000。
分析:用已有模版A * b (大数乘小数)模拟A * B(大数乘大数),可以将B进行拆解,每次只用A乘B的一位,再将每次得到的结果处理后相加即可
输入:
A = 123(逆序 [3,2,1])
B = 45(逆序 [5,4])
循环过程:
i=0(处理 B 的个位 5)
D = 123×5 = 615 → 逆序 [5,1,6]
移位 i=0 → 保持 D=[5,1,6]
C = [5,1,6](值 615)
i=1(处理 B 的十位 4)
D = 123×4 = 492 → 逆序 [2,9,4]
移位 i=1 → 插入 1 个零 → [0,2,9,4](值 4920)
累加后 C = [5,1,6] + [0,2,9,4] = [5,3,5,5](值 615+4920=5535)
最终输出:
C 逆序输出 5535
#include<iostream>
#include<vector>
using namespace std;
vector<int> mul(vector<int> &A, int b) //高精度乘法
{vector<int> C;int t = 0;for(size_t i = 0; i < A.size() || t; i++){if(i < A.size()) t += A[i] * b;C.push_back(t % 10);t /= 10;}return C;
}
vector<int> add(vector<int> &A, vector<int> &B) //高精度加法
{vector<int> C;int t = 0;for(size_t 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) C.push_back(1);return C;
}
vector<int> solve(vector<int> &A, vector<int> &B)
{vector<int> C;//遍历 B 的每一位(从低位到高位)for(size_t i = 0; i < B.size(); i++){//计算A与当前位的乘积vector<int> D = mul(A, B[i]);//移位处理:在结果后添加i个0//因为存储是逆序的,所以先添加尾部的0,再添加逆序的数字vector<int> temp(i, 0); // 创建 i 个前导零(逆序存储时零在低位)temp.insert(temp.end(), D.begin(), D.end()); // 拼接乘积结果D = temp; // 完成移位,相当于 D × 10^iC = add(C, D);}//逆序存储,从结果尾部删除前导0while(C.size() > 1 && C.back() == 0) C.pop_back(); return C;
}
int main()
{string a, b;vector<int> A, B;cin >> 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 C = solve(A, B);for(int i = C.size() - 1; i >= 0; i--)printf("%d", C[i]);return 0;
}
例题2(P1009 阶乘之和)
题目描述
用高精度计算出 S=1!+2!+3!+⋯+n!(n≤50)。
其中 ! 表示阶乘,定义为 n!=n×(n−1)×(n−2)×⋯×1。例如,5!=5×4×3×2×1=120。
输入格式
一个正整数 n。
输出格式
一个正整数 S,表示计算结果。
分析:Sn = a1+ a2 + a3 + a4 + ... + an,其中an = a(n-1) * n,直接套用乘法与加法模版
#include<iostream>
#include<vector>
using namespace std;
vector<int> add(vector<int> &A, vector<int> &B)
{vector<int> C;int t = 0;for(size_t i = 0; i < A.size() || i < B.size() || t; i++){if(i < A.size()) t += A[i];if(i < B.size()) t += B[i];C.push_back(t % 10);t /= 10;}return C;
}
vector<int> mul(vector<int> &A, int b)
{vector<int> C;int t = 0;for(size_t i = 0; i < A.size() || t; i++){if(i < A.size()) t += A[i] * b;C.push_back(t % 10);t /= 10;}while(C.size() > 1 && C.back() == 0) C.pop_back();return C;
}
int main()
{int n;cin >> n;vector<int> S, A; //S存储累加和,A存储当前阶乘值S.push_back(0); //初始化和S=0A.push_back(1); //初始化阶乘A=1for(int i = 1; i <= n; i++){A = mul(A, i); //计算A = i!S = add(S, A); //将当前阶乘值i!累加到总和S}for(int i = S.size() - 1; i >= 0; i--)printf("%d", S[i]);return 0;
}
例题3(P1045 麦森数)
题目描述
形如 2^P−1 的素数称为麦森数,这时 P 一定也是个素数。但反过来不一定,即如果 P 是个素数,2^P−1 不一定也是素数。到 1998 年底,人们已找到了 37 个麦森数。最大的一个是 P=3021377,它有 909526 位。麦森数有许多重要应用,它与完全数密切相关。
任务:输入 P(1000<P<3100000),计算 2^P−1 的位数和最后 500 位数字(用十进制高精度数表示)
输入格式
文件中只包含一个整数 P(1000<P<3100000)
输出格式
第一行:十进制高精度数 2^P−1 的位数。
第 2∼11 行:十进制高精度数 2^P−1 的最后 500 位数字。(每行输出 50 位,共输出 10 行,不足 500 位时高位补 0)
不必验证 2^P−1 与 P 是否为素数。
输入输出样例
输入:
1279输出:
386
00000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000
00000000000000104079321946643990819252403273640855
38615262247266704805319112350403608059673360298012
23944173232418484242161395428100779138356624832346
49081399066056773207629241295093892203457731833496
61583550472959420547689811211693677147548478866962
50138443826029173234888531116082853841658502825560
46662248318909188018470682222031405210266984354887
32958028878050869736186900714720710555703168729087
分析:
计算数 N 的位数 D:D=⌊log10N⌋+1
1000<P<3100000,很明显我们不能直接计算2^p-1,数值巨大,题中提示我们只需计算最后五百位数字
#include<iostream>
#include<vector>
#include<cmath>
using namespace std;
vector<int> mul(vector<int> &A, int b)
{vector<int> C;int t = 0;for(size_t i = 0; i < A.size() || t; i++){if(i < A.size()) t += A[i] * b;C.push_back(t % 10);t /= 10;if(C.size() > 500) break; //超过500位结束}while(C.back() == 0 && C.size() > 1) C.pop_back();return C;
}
vector<int> sub(vector<int> &A, int b)
{int t = 0;for(size_t i = 0; i < A.size(); i++){t = A[i] - t;if(i < 1) t -= b;A[i] = (t + 10) % 10;if(t < 0) t = 1;else break; //借位处理完成后提前退出}while(A.back() == 0 && A.size() > 1) A.pop_back();return A;
}int main()
{long long p;cin >> p;long long n = p * log10(2) + 1;cout << n << endl;vector<int> A(1, 1); //初始化为1for(;p > 13; p = p - 13){A = mul(A, 8192); //每次乘2^13,减少循环次数}A = mul(A, pow(2, p));//计算2^p - 1A = sub(A, 1);// 输出低500位(不足补0)for(int i = 499; i >= 0; i--) {if(static_cast<size_t>(i) < A.size()) cout << A[i];else cout << 0;if(i % 50 == 0) cout << endl;}return 0;
}