高精度
前言
前两天考完了csp认证,总的来说还算正常发挥 休息了两天给大家更一个蓝桥杯
这个高精度是啥东西 说白了就是我们一般的数据类型存储不了的数字进行运算
具体来说就是10...(此处省略500个0),即便是强如 long long 也是存不下的
那么解决办法是啥?不算了?
显然不是 我们可以模拟小学学到竖式来计算 我们来分别来具体看加减乘除
我这里用例题来呈现
具体的讲解视频我附在这里 讲的真的太好了 我感觉就算一点没学过算法也能听明白
高精度加法
例题
思路及代码
solution 1(初阶版 40分)

我的注释写的非常详细了 但是由于我没有对特殊情况做出处理导致才过了2个样例
这里样例比较少 但是大部分情况下代码是没问题的
cpp
#include <bits/stdc++.h>
using namespace std;
int main()
{
string s1,s2;
//对于string类型会自动初始化为空字符串
//对于数组来说如果不对他初始化 他会是一个随机值
int a1[110], a2[110], a3[110]={0};
//最长是500位加上500位 结果最多是一个501位数 所以大小我们定义510肯定够了 a1 a2 a3分别保存逆序存放的数字和结果
//读入我们输入的数字存进字符串里面
cin>>s1;
cin>>s2;
//将读入的数字逆序存放在数组里面
//例如我们将123的1存到数组里面 它的位置就是
//a1[s1.size()-1-0]
for(int i=0;i<s1.size();i++)
{
a1[s1.size()-i-1]=s1[i]-'0';
//-i是因为倒序 -1是因为数组下标从0开始;
}
for(int i=0;i<s2.size();i++)
{
a2[s2.size()-i-1]=s2[i]-'0';
}
//求出循环次数也就是我们模拟竖式相加的次数 而在加法中这个是由较大的数来决定的 这里不用考虑负数也就是字符串较长的数字
int len=s1.size();
if(s2.size()>s1.size())
{
len=s2.size();
}
//相加操作
for(int i=0;i<len;i++)
{
a3[i]=a1[i]+a2[i];
}
//满10进位
for(int i=0;i<len;i++)
{
if(a3[i]>10)
{
a3[i+1]+=a3[i]/10;
a3[i]=a3[i]%10;
}
}
//逆序输出
//因为我们一开始将数组置为0 所以我们只需要找到第一个不为0的点
//就可以找到逆序输出的起点
if(a3[len]!=0)//判断一下最高位置是不是0
{
len++;
}
for(int i=len-1;i>=0;i--)
{
cout<<a3[i];
}
}
solution 2(完全体 AC)
首先优化的点在于将上面代码中的对应相加和进位操作放在了一个循环里面
第二个就是比较重要的点 就是如果结果是0 我们需要只输出一个0 也就是说去掉前序位置上的所有0
cpp
#include <bits/stdc++.h>
using namespace std;
int main() {
std::ios::sync_with_stdio(false); // 提高输入输出效率
std::cin.tie(NULL);
string s1, s2;
cin >> s1 >> s2;
int a1[510] = {0}, a2[510] = {0}, a3[510] = {0};
// 将字符串逆序存入数组
for(int i = 0; i < s1.size(); i++) {
a1[s1.size() - i - 1] = s1[i] - '0';
}
for(int i = 0; i < s2.size(); i++) {
a2[s2.size() - i - 1] = s2[i] - '0';
}
// 求出最大长度
int len = max(s1.size(), s2.size());
// 相加并处理进位
int carry = 0;
for(int i = 0; i < len || carry; i++) {
if(i < len) a3[i] += a1[i] + a2[i];
if(a3[i] >= 10) {
carry = a3[i] / 10;
a3[i] %= 10;
a3[i+1] += carry;
} else {
carry = 0;
}
}
// 输出结果
int start = len;
while(start >= 0 && a3[start] == 0) start--;
if(start == -1) cout << "0"; // 特殊情况:结果为0
else {
for(int i = start; i >= 0; i--) {
cout << a3[i];
}
}
return 0;
}
高精度乘法
例题
思路及代码
solution 1(TLE 但是代码很清晰)
cpp
#include <bits/stdc++.h>
using namespace std;
//首先这是一道高精度乘法(而且是高精度乘以高精度)
//我们需要用其中一个数的每一位去乘以另外一个高精度数
//大体上的思路就是用两层循环来实现 内层循环a1外层循环a2
int main()
{
//string s1=""可以不初始化 string类型默认初始化为空字符串
string s1,s2;
int a1[2010],a2[2010],a3[4020]={0};
getline(cin,s1);
getline(cin,s2);
//逆序存放在数组当中
for(int i=0;i<s1.size();i++)
{
a1[i]=s1[s1.size()-i-1]-'0';
}
for(int j=0;j<s2.size();j++)
{
a2[j]=s2[s2.size()-j-1]-'0';
}
for(int i=0;i<s2.size();i++)
{
for(int j=0;j<s1.size();j++)
{
//第一次a3[j+0]=a3[j+0]+a1[j]*a2[i]
//第二次a3[j+1]=a3[j+1]+a1[j]*a2[i]
//通过找规律我们可以知道我们每一轮的变化就是
a3[j+i]=a3[j+i]+a1[j]*a2[i];
//进位判断
if(a3[j+i]>10)
{
a3[j+i+1]+=a3[j+i]/10;//看看能进几
a3[j+i]=a3[j+i]%10;
}
}
}
//完成相乘操作之后 我们就需要逆序输出一下a3
//首先我们需要明确的就是一个200✖️200的结果不会超过400位数/
//也就是这两个长度之和
int index=0;
for(int i=s1.size()+s2.size();i>=0;i--)
{
if(a3[i]!=0)
{
index=i;//找到了起始下标
break;
}
}
for(int i=index;i>=0;i--)
{
cout<<a3[i];
}
return 0;
}
solution 1的问题
- 算法复杂度问题
- 当前复杂度 :你的代码使用了两层嵌套循环来实现高精度乘法,外层循环遍历
s2
的每一位,内层循环遍历s1
的每一位。假设s1
和s2
的长度分别为m
和n
,那么时间复杂度为 O(m × n)。 - 问题点:如果输入的字符串长度较大(例如接近 2000),则计算量会非常大,导致超时。
- 进位逻辑效率问题
-
当前实现:在每次相乘后立即进行进位操作:
cppif(a3[j+i] > 10) { a3[j+i+1] += a3[j+i] / 10; a3[j+i] = a3[j+i] % 10; }
这种方式会在每次相乘后都检查是否需要进位,增加了不必要的开销。
-
优化方法:可以先完成所有乘法操作,最后统一处理进位。这样可以减少重复的进位判断。
- 输出部分的效率问题
-
当前实现:
cppfor(int i = s1.size() + s2.size(); i >= 0; i--) { if(a3[i] != 0) { index = i; // 找到起始下标 break; } } for(int i = index; i >= 0; i--) { cout << a3[i]; }
使用了两个循环来找到第一个非零位并逆序输出结果。
-
优化方法:可以通过一次循环完成查找和输出,减少冗余操作。
- 输入方式的问题
-
当前实现:
cppgetline(cin, s1); getline(cin, s2);
使用
getline
读取输入字符串,虽然功能正确,但在某些评测环境中可能会比cin
稍慢。 -
优化方法 :如果输入不包含空格,可以直接使用
cin
提高效率。
solution 2(优化 AC)
cpp
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false); // 不用管就是 提高输入输出效率
cin.tie(NULL);
string s1, s2;
cin >> s1 >> s2; // 使用 cin 提高效率
int a1[2010] = {0}, a2[2010] = {0}, a3[4020] = {0};
// 将字符串逆序存入数组
for(int i = 0; i < s1.size(); i++) {
a1[i] = s1[s1.size() - i - 1] - '0';
}
for(int i = 0; i < s2.size(); i++) {
a2[i] = s2[s2.size() - i - 1] - '0';
}
// 高精度乘法
for(int i = 0; i < s2.size(); i++) {
for(int j = 0; j < s1.size(); j++) {
a3[i + j] += a1[j] * a2[i]; // 先累加结果
}
}
// 统一处理进位
for(int i = 0; i < s1.size() + s2.size(); i++) {
if(a3[i] >= 10) {
a3[i + 1] += a3[i] / 10;
a3[i] %= 10;
}
}
// 输出结果
int start = s1.size() + s2.size();
while(start > 0 && a3[start] == 0) start--; // 去掉前导零
if(start == -1) cout << "0"; // 特殊情况:结果为0
else {
for(int i = start; i >= 0; i--) {
cout << a3[i];
}
}
return 0;
}
高精度减法
例题

代码及思路
solution 1 (90分)
应该是对于结果正好为0的时候没有特判 补一下
cpp
#include <bits/stdc++.h>
using namespace std;
int main()
{
string s1,s2;
cin>>s1;
cin>>s2;
int a1[10100],a2[10100],a3[10100]={0};
char flag ='+';
//保证始终都是位数长的减去小的
//比如现在让你求10-200 我们可以算200-10 然后在结果前面加上负号
//如果这个flag是正的则不需要输出
if(s1.size()<s2.size()||(s1.size()==s2.size()&& s1<s2))
{
swap(s1,s2);
flag='-';
}
//逆序存储
for(int i=0;i<=s1.size();i++)
{
a1[s1.size()-i-1]=s1[i]-'0';
}
for(int i=0;i<=s2.size();i++)
{
a2[s2.size()-i-1]=s2[i]-'0';
}
for(int i=0;i<s1.size();i++)
{
//先借好位
if(a1[i]<a2[i])//需要借位的情况
{
a1[i]+=10;
a1[i+1]--;
}
//再对应相减
a3[i]=a1[i]-a2[i];
}
if(flag=='-')
{
cout<<flag;
}
int index=0;//找到倒序输出的起始位置
for(int i=s1.size()-1;i>=0;i--)
{
if(a3[i]!=0)
{
index=i;
break;
}
}
for(int i=index;i>=0;i--)
{
cout<<a3[i];
}
return 0;
}
问题
- 数组越界问题
-
问题点:
cppfor(int i=0;i<=s1.size();i++) { a1[s1.size()-i-1]=s1[i]-'0'; }
这里的循环条件是
i <= s1.size()
,但字符串的索引范围是[0, s1.size()-1]
,因此当i == s1.size()
时会导致数组越界。 -
修复方法 :将循环条件改为
i < s1.size()
。
- 借位逻辑错误
-
问题点:
cppif(a1[i] < a2[i]) { a1[i] += 10; a1[i+1]--; }
如果
a1[i+1]
已经为 0,则再次借位会导致负值,从而产生错误结果。 -
修复方法:在借位时确保高位有足够值可以借。可以通过递归借位的方式解决:
cppif(a1[i] < a2[i]) { int j = i; while(j < s1.size() && a1[j+1] == 0) { a1[j+1] = 9; // 借位后恢复为9 j++; } if(j < s1.size()) { a1[j+1]--; a1[i] += 10; } }
- 前导零问题
-
问题点:
cppfor(int i=s1.size()-1;i>=0;i--) { if(a3[i] != 0) { index = i; break; } }
如果结果为 0(例如
1 - 1
),则index
会保持初始值0
,导致输出多余字符。 -
修复方法 :在输出前检查是否所有位均为 0,并直接输出
0
:cppbool all_zero = true; for(int i=0;i<s1.size();i++) { if(a3[i] != 0) { all_zero = false; break; } } if(all_zero) { cout << "0"; return 0; }
- 符号处理问题
-
问题点 :如果两个数相等(例如
1 - 1
),当前代码会输出-0
,这是不正确的。 -
修复方法 :在输出符号前检查结果是否为 0:
cppif(flag == '-') { bool all_zero = true; for(int i=0;i<s1.size();i++) { if(a3[i] != 0) { all_zero = false; break; } } if(!all_zero) { cout << flag; } }
solution 2
cpp
#include <bits/stdc++.h>
using namespace std;
int main() {
string s1, s2;
cin >> s1 >> s2;
int a1[10100] = {0}, a2[10100] = {0}, a3[10100] = {0};
char flag = '+';
// 保证始终都是位数长的减去小的
if(s1.size() < s2.size() || (s1.size() == s2.size() && s1 < s2)) {
swap(s1, s2);
flag = '-';
}
// 逆序存储
for(int i = 0; i < s1.size(); i++) {
a1[s1.size() - i - 1] = s1[i] - '0';
}
for(int i = 0; i < s2.size(); i++) {
a2[s2.size() - i - 1] = s2[i] - '0';
}
// 高精度减法
for(int i = 0; i < s1.size(); i++) {
if(a1[i] < a2[i]) { // 需要借位的情况
int j = i;
while(j < s1.size() && a1[j+1] == 0) {
a1[j+1] = 9; // 借位后恢复为9
j++;
}
if(j < s1.size()) {
a1[j+1]--;
a1[i] += 10;
}
}
a3[i] = a1[i] - a2[i];
}
// 检查是否所有位均为0
bool all_zero = true;
for(int i = 0; i < s1.size(); i++) {
if(a3[i] != 0) {
all_zero = false;
break;
}
}
if(all_zero) {
cout << "0";
return 0;
}
// 输出符号
if(flag == '-') {
bool has_non_zero = false;
for(int i = 0; i < s1.size(); i++) {
if(a3[i] != 0) {
has_non_zero = true;
break;
}
}
if(has_non_zero) {
cout << flag;
}
}
// 找到倒序输出的起始位置
int index = 0;
for(int i = s1.size() - 1; i >= 0; i--) {
if(a3[i] != 0) {
index = i;
break;
}
}
// 输出结果
for(int i = index; i >= 0; i--) {
cout << a3[i];
}
return 0;
}