高精度计算
基本原理:对于很大很大的数,用longlong都处理不了的时候,就采用高精度计算方法,主要思想就是把数据存进——字符串/数组里,然后用加法减法的基本原理一位一位地加减,再把结果存进字符串/数组里。在这个过程中所有的大数都不再是一个数,而是一串字符。
一、高精度加法
(字符串做法)
#include<stdio.h>
#include<string>
#include<string.h>
#include<iostream>
using namespace std;
//高精度加法
//只能是两个正数相加
string add(string str1, string str2)//高精度加法
{
string str;
int len1 = str1.length();
int len2 = str2.length();
//前面补0,弄成长度相同
if (len1 < len2)
{
for (int i = 1; i <= len2 - len1; i++)
str1 = "0" + str1; //在前面添0哦
}
else
{
for (int i = 1; i <= len1 - len2; i++)
str2 = "0" + str2;
}
len1 = str1.length(); //现在是一样的
int cf = 0; //进位标志,最开始是0
int temp; //存每一次计算后的个位
for (int i = len1 - 1; i >= 0; i--) //从最后一位开始做加法
{
temp = str1[i] - '0' + str2[i] - '0' + cf; //这里是强制转换,得到的是两数同一位的和+cf
cf = temp / 10; //前一位的进位标志,如果cf>10,说明要进位呀
temp %= 10; //进位之后的两数和的个位
str = char(temp + '0') + str; //再一次强制转换,把刚刚算出来的个位存进str里
}
if (cf != 0) str = char(cf + '0') + str; //全部循环完之后,查看最前面一位的进位标志是否为1
return str; //返回结果,是string格式的
}
int main()
{
string a, b;
cin >> a >> b;
string out;
out = add(a, b); //调用,非常方便
cout << out;
}
二、高精度减法
- 输入两个正数,不规定顺序,在函数里面自己判断了顺序)
- 和加法大同小异,但是这里因为会涉及到输出的数可能是00123,000,…这些情况,所以采取下面的函数:
- find_first_not_of ( str ,num ) 函数 【还可以是last】
从字符串第 num + 1 个字符开始查找第一个与str中的字符不匹配的字符,返回它的位置。缺省时从头开始。如果没找到就返回string::nops - str.erase ( a , b ) 函数
删除 [ a , b )包含的字符 - 组合使用:str.erase(0, str.find_first_not_of(‘0’)); 即可删掉00123前面的00.
- 但是如果是000就会被全部删掉了,所以要在最后加个判断,如果str.empty(),就令输出为0.
- find_first_not_of ( str ,num ) 函数 【还可以是last】
#include<stdio.h>
#include<stdio.h>
#include<string>
#include<string.h>
#include<iostream>
using namespace std;
int len;
//高精度减法
string sub(string str1, string str2)
{
string str;
int len1 = str1.length();
int len2 = str2.length();
//前面补0,弄成长度相同
if (len1 < len2)
{
for (int i = 1; i <= len2 - len1; i++)
str1 = "0" + str1; //在前面添0哦
}
else
{
for (int i = 1; i <= len1 - len2; i++)
str2 = "0" + str2;
}
len = str1.length(); //现在是一样的
//补完之后判断一下大小,存进a和b里
string a, b;
int flag = 0; // 正负号标志
a = str1;
b = str2;
for (int i = 0; i < len; i++)
{
if (str1[i] > str2[i])
{
a = str1;
b = str2;
break;
}
if (str1[i] < str2[i])
{
a = str2;
b = str1;
flag = 1; //表示结果是一个负数哦,要记得在后面填上-
break;
}
}
int cf = 0; //进位标志,最开始是0
int temp; //存每一次计算后的个位
for (int i = len - 1; i >= 0; i--) //从最后一位开始做减法
{
temp = (a[i] - '0') + 10 - (b[i] - '0') - cf; //这里是强制转换,得到的是两数同一位的和+cf
cf = temp >= 10 ? 0 : 1; //前一位的借位标志,如果cf>10,说明不用借位呀
temp %= 10; //两数差的个位
str = char(temp + '0') + str; //再一次强制转换,把刚刚算出来的个位存进str里
}
str.erase(0, str.find_first_not_of('0'));
if (flag == 1)str = "-" + str;
return str; //返回结果,是string格式的
}
int main()
{
string a, b;
cin >> a >> b;
string out;
out = sub(a, b); //调用,非常方便
if (out.empty()) cout << 0;
else cout << out;
}
三、高精度乘法
原理就是str1与str2的每一位相乘,得到str2的位数的个数的tempstr(要在后面依次添0、1、2…个0),然后调用高精加,就可以啦!!
#include<stdio.h>
#include<string>
#include<string.h>
#include<iostream>
using namespace std;
int len;
//高精度加法
//只能是两个正数相加
string add(string str1, string str2)//高精度加法
{
string str;
int len1 = str1.length();
int len2 = str2.length();
//前面补0,弄成长度相同
if (len1 < len2)
{
for (int i = 1; i <= len2 - len1; i++)
str1 = "0" + str1; //在前面添0哦
}
else
{
for (int i = 1; i <= len1 - len2; i++)
str2 = "0" + str2;
}
len1 = str1.length(); //现在是一样的
int cf = 0; //进位标志,最开始是0
int temp; //存每一次计算后的个位
for (int i = len1 - 1; i >= 0; i--) //从最后一位开始做加法
{
temp = str1[i] - '0' + str2[i] - '0' + cf; //这里是强制转换,得到的是两数同一位的和+cf
cf = temp / 10; //前一位的进位标志,如果cf>10,说明要进位呀
temp %= 10; //进位之后的两数和的个位
str = char(temp + '0') + str; //再一次强制转换,把刚刚算出来的个位存进str里
}
if (cf != 0) str = char(cf + '0') + str; //全部循环完之后,查看最前面一位的进位标志是否为1
return str; //返回结果,是string格式的
}
//高精度乘法
//只能是两个正数相乘(会用到高精度加)
string mul(string str1, string str2)
{
string str;
int len1 = str1.length();
int len2 = str2.length();
string tempstr;
for (int i = len2 - 1; i >= 0; i--)
{
tempstr = ""; //tempstr是表示乘法算出来的中间的值,str2有多少位,就有多少个tempstr,所以每次都要重新定义
int temp = str2[i] - '0'; //取str2的最后一位的值
int t = 0;
int cf = 0;
if (temp != 0) //如果这一位是0,当然不需要计算了
{
for (int j = 1; j <= len2 - 1 - i; j++) //注意,除了第一个,要在tempstr后面添1个、2个...0
tempstr += "0";
for (int j = len1 - 1; j >= 0; j--) //把str1拿来进行计算
{
t = (temp * (str1[j] - '0') + cf) % 10; //相乘+进位,再取余,得到个位
cf = (temp * (str1[j] - '0') + cf) / 10; //得到进位
tempstr = char(t + '0') + tempstr; //在tempstr前面加上这一位的个位
}
if (cf != 0) tempstr = char(cf + '0') + tempstr;
}
str = add(str, tempstr); //这是乘法的步骤,就是吧tempstr相加(已经在后面添过0了)
}
str.erase(0, str.find_first_not_of('0')); //保险起见,删除掉str第一个非0数字前面的所有0
return str;
}
int main()
{
string a, b;
cin >> a >> b;
if (a == "0" || b == "0") //防止有一个为0的时候计算出错...(本来不想写的,谁会用0去乘还要用高精乘啊?结果提交居然WA了)
cout << 0;
else
{
string out;
out = mul(a, b); //调用,非常方便
cout << out;
}
}
四、高精度除法
摆一个函数在这里,会用到高精加和高精乘
//高精度除法
//两个正数相除,商为quotient,余数为residue
//需要高精度减法和乘法
void div(string str1,string str2,string "ient,string &residue)
{
quotient=residue="";//清空
if(str2=="0")//判断除数是否为0
{
quotient=residue="ERROR";
return;
}
if(str1=="0")//判断被除数是否为0
{
quotient=residue="0";
return;
}
int res=compare(str1,str2);
if(res<0)
{
quotient="0";
residue=str1;
return;
}
else if(res==0)
{
quotient="1";
residue="0";
return;
}
else
{
int len1=str1.length();
int len2=str2.length();
string tempstr;
tempstr.append(str1,0,len2-1);
for(int i=len2-1;i<len1;i++)
{
tempstr=tempstr+str1[i];
tempstr.erase(0,tempstr.find_first_not_of('0'));
if(tempstr.empty())
tempstr="0";
for(char ch='9';ch>='0';ch--)//试商
{
string str,tmp;
str=str+ch;
tmp=mul(str2,str);
if(compare(tmp,tempstr)<=0)//试商成功
{
quotient=quotient+ch;
tempstr=sub(tempstr,tmp);
break;
}
}
}
residue=tempstr;
}
quotient.erase(0,quotient.find_first_not_of('0'));
if(quotient.empty()) quotient="0";
}
五、一道例题
- 分析:
- 对于n可能达到50的情况,稍微估算就知道需要用到高精度计算。题目也很好理解,就是不断的乘和加。
- 这里沿用了上面高精加和高精乘的函数,仅仅是进行了调用,这样虽然理解比较简单但是时间会多一些,更好的方法是在字符串计算的时候就进行阶乘求和这一步。换句话说,就是实现多个数字的高精加和高精乘,这个之后如果有缘还会进行完善。
- int转字符串的做法:
std::string in = std::to_string(1005);
s
#include<stdio.h>
#include<string>
#include<string.h>
#include<iostream>
using namespace std;
int len;
//高精度加法
//只能是两个正数相加
string add(string str1, string str2)//高精度加法
{
string str;
int len1 = str1.length();
int len2 = str2.length();
//前面补0,弄成长度相同
if (len1 < len2)
{
for (int i = 1; i <= len2 - len1; i++)
str1 = "0" + str1; //在前面添0哦
}
else
{
for (int i = 1; i <= len1 - len2; i++)
str2 = "0" + str2;
}
len1 = str1.length(); //现在是一样的
int cf = 0; //进位标志,最开始是0
int temp; //存每一次计算后的个位
for (int i = len1 - 1; i >= 0; i--) //从最后一位开始做加法
{
temp = str1[i] - '0' + str2[i] - '0' + cf; //这里是强制转换,得到的是两数同一位的和+cf
cf = temp / 10; //前一位的进位标志,如果cf>10,说明要进位呀
temp %= 10; //进位之后的两数和的个位
str = char(temp + '0') + str; //再一次强制转换,把刚刚算出来的个位存进str里
}
if (cf != 0) str = char(cf + '0') + str; //全部循环完之后,查看最前面一位的进位标志是否为1
return str; //返回结果,是string格式的
}
//高精度乘法
//只能是两个正数相乘(会用到高精度加)
string mul(string str1, string str2)
{
string str;
int len1 = str1.length();
int len2 = str2.length();
string tempstr;
for (int i = len2 - 1; i >= 0; i--)
{
tempstr = ""; //tempstr是表示乘法算出来的中间的值,str2有多少位,就有多少个tempstr,所以每次都要重新定义
int temp = str2[i] - '0'; //取str2的最后一位的值
int t = 0;
int cf = 0;
if (temp != 0) //如果这一位是0,当然不需要计算了
{
for (int j = 1; j <= len2 - 1 - i; j++) //注意,除了第一个,要在tempstr后面添1个、2个...0
tempstr += "0";
for (int j = len1 - 1; j >= 0; j--) //把str1拿来进行计算
{
t = (temp * (str1[j] - '0') + cf) % 10; //相乘+进位,再取余,得到个位
cf = (temp * (str1[j] - '0') + cf) / 10; //得到进位
tempstr = char(t + '0') + tempstr; //在tempstr前面加上这一位的个位
}
if (cf != 0) tempstr = char(cf + '0') + tempstr;
}
str = add(str, tempstr); //这是乘法的步骤,就是吧tempstr相加(已经在后面添过0了)
}
str.erase(0, str.find_first_not_of('0')); //保险起见,删除掉str第一个非0数字前面的所有0
return str;
}
//阶乘
string mull(int n)
{
if (n == 1)
return "1";
if (n == 2)
return "2";
string tmp = "2"; //结果,最先是2的阶乘
if (n >= 3)
{
for (int i = 3; i <= n; i++)
{
//把自身存进in里
std::string in = std::to_string(i);
tmp = mul(tmp, in);
}
}
return tmp;
}
//阶乘求和
string mull_add(int n)
{
if (n == 1)
return "1";
string out = "1";
if (n > 1)
{
for (int i = 2; i <= n; i++)
{
string a = mull(i); //求一下自身的阶乘
out = add(a, out); //求和
}
}
return out;
}
int main()
{
int n;
cin >> n;
string out;
out = mull_add(n); //调用,非常方便
cout << out;
}
六、当高精度TLE了…
一道简单的例题
分析很简单,就是类似斐波那契数列的递归,但是因为数据很大Nmax=5000,所以理所当然想到需要用到高精。
首先来一种粗暴的高精加的做法(下面已经是改进过的了,但是仍然TLE了,…当N比较小的时候是可以的,但是一旦N超过30就已经跑不动了…我觉得这种方法其实是有可取之处的,并不是暴力地每次都调用高精加,于是我把代码先放在这里吧,如果能在这个基础上修改应该会比较快):
#include<stdio.h>
#include<string>
#include<string.h>
#include<iostream>
using namespace std;
int len;
//高精度加法
//只能是两个正数相加
string add(string str1, string str2)//高精度加法
{
string str;
int len1 = str1.length();
int len2 = str2.length();
//前面补0,弄成长度相同
if (len1 < len2)
{
for (int i = 1; i <= len2 - len1; i++)
str1 = "0" + str1; //在前面添0哦
}
else
{
for (int i = 1; i <= len1 - len2; i++)
str2 = "0" + str2;
}
len1 = str1.length(); //现在是一样的
int cf = 0; //进位标志,最开始是0
int temp; //存每一次计算后的个位
for (int i = len1 - 1; i >= 0; i--) //从最后一位开始做加法
{
temp = str1[i] - '0' + str2[i] - '0' + cf; //这里是强制转换,得到的是两数同一位的和+cf
cf = temp / 10; //前一位的进位标志,如果cf>10,说明要进位呀
temp %= 10; //进位之后的两数和的个位
str = char(temp + '0') + str; //再一次强制转换,把刚刚算出来的个位存进str里
}
if (cf != 0) str = char(cf + '0') + str; //全部循环完之后,查看最前面一位的进位标志是否为1
return str; //返回结果,是string格式的
}
//高精度乘法
//只能是两个正数相乘(会用到高精度加)
string mul(string str1, string str2)
{
string str;
int len1 = str1.length();
int len2 = str2.length();
string tempstr;
for (int i = len2 - 1; i >= 0; i--)
{
tempstr = ""; //tempstr是表示乘法算出来的中间的值,str2有多少位,就有多少个tempstr,所以每次都要重新定义
int temp = str2[i] - '0'; //取str2的最后一位的值
int t = 0;
int cf = 0;
if (temp != 0) //如果这一位是0,当然不需要计算了
{
for (int j = 1; j <= len2 - 1 - i; j++) //注意,除了第一个,要在tempstr后面添1个、2个...0
tempstr += "0";
for (int j = len1 - 1; j >= 0; j--) //把str1拿来进行计算
{
t = (temp * (str1[j] - '0') + cf) % 10; //相乘+进位,再取余,得到个位
cf = (temp * (str1[j] - '0') + cf) / 10; //得到进位
tempstr = char(t + '0') + tempstr; //在tempstr前面加上这一位的个位
}
if (cf != 0) tempstr = char(cf + '0') + tempstr;
}
str = add(str, tempstr); //这是乘法的步骤,就是吧tempstr相加(已经在后面添过0了)
}
str.erase(0, str.find_first_not_of('0')); //保险起见,删除掉str第一个非0数字前面的所有0
return str;
}
//数阶梯,n=3的时候的方法是n=1和n=2的和;n=4的时候的方法是n=3和n=2的和....
//即n的方法是n-1和n-2的和
string add1(string in) //只加1的高精加法
{
string out = "";
int cf = 0;
int len = in.length();
for (int i = len - 1; i >= 0; i--)
{
int temp;
if (i == len - 1) temp = int(in[i] - '0') + 1;
else temp = int(in[i] - '0') + cf;
if (temp < 10)
{
cf = 0;
out = char(temp + '0') + out;
}
else
{
cf = 1;
out = char(temp % 10 + '0') + out;
}
}
if (cf == 1) out = "1" + out;
return out;
}
string out1 = "0";
string out2 = "0";
void feib(int n) //统计1和2各自出现多少次
{
if (n == 1)
{
out1 = add1(out1);
}
else if (n == 2)
{
out2 = add1(out2);
}
else
{
feib(n - 1);
feib(n - 2);
}
}
int main()
{
int n;
cin >> n;
feib(n);
string temp = mul("2", out2); //2出现out2次,每次有2种
string ans = add(out1, temp); //1和2相加
cout << ans;
}
压位高精加
概念
压位高精加,顾名思义,就是将高精度数组的每个位存多点。
优点
因为高精度其实是模拟竖式计算的方法,一个变量存一位,也就是0…9,这样有点浪费时间,而当n,m太大时则会超时,这个时候,就引申除了压位高精加。
实现
对高精度略作修改,将原来的%10、/10,改成了%100000000,/100000000,但是输出的时候要记得处理前面的0,例如 9999999999999999+2=10000000000000001
在数组里面它是这样的:101
所以,要判断当它不是第一个数字的时候,要添加0,至于添加几个,则看它有多大。判断可以直接暴力,也可以数学方法(log)去算,从而添加0,例如1=7个0+1 10=6个0+1,100=5个0+1……以此类推。