高精度算法解析和高精度赛场用模板
1.高精度算法介绍
高精度算法,又称数值处理算法。意在通过数组模拟较大整数的存储和使用。本篇文章就会给大家介绍赛场上使用的精简版高精度算法模版。
2.高精度结构体前置函数
2.1.定义高精度结构体
首先定义一个高精度结构体。用数组a储存这个数,用变量len
储存这个数的长度。MAXN代表数组a的最大长度。
#define MAXN 1010
struct bigint
{
int len,a[MAXN];
};
2.2.重载[ ]
为了方便访问数组a中的元素,我们需要重载[ ]。
int &operator[](int i)//用x[i]代替x.a[i]
{
return a[i];
}
2.2.构造函数
我们有时会需要用低精度整数来初始化高精度整数。这个时候就需要构造函数了。注意:为了方便实用,数组a倒序存储这个数字,即a[1]代表这个数的最后一位,以此类推。
bigint(int x=0)//默认这个数是0
{
memset(a,0,sizeof(a));//初始化数组a
if(x==0)//如果x是0会直接跳过底下的循环,导致len被初始化为0,所以要特判
{
len=1;
return;//直接返回
}
for(len=1;x;len++)//只要x没被取完就一直去并增加这个数的长度
a[len]=x%10,x/=10;//倒序存储x
len--;//最后len总会多1
}
2.3.输入输出
我们用一个字符串模拟输入。然后倒序存储。输出就直接倒序输出。
void in_data()//输入高精度整数
{
string s;cin>>s;//暂时存储到字符串里面
memset(a,0,sizeof(a));len=0;//清空数组并初始化这个大整数
for(int i=s.length()-1;i>=0;i--,len++)//字符串转整数
a[s.length()-i]=s[i]-'0';
}
void print()//打印这个数字,注意数组a是倒着存储各个数位的
{
//注意在后面的处理中可能会出现len=0的情况,所以i的初始值要写成max(num.len, 1)
for(int i=max(len,1);i>=1;i--)
printf("%d",a[i]);//前面重载过[]了,等价于num.a[i]
}
2.4.展开
在后面的处理中,我们不会直接处理进位,而是在最后使用flatten
函数一口气处理。我们叫它“展开”。
void flatten(int L)//处理1到L范围内的进位并重置长度,需要保证L不小于有效长度
{
len=L;//先赋一个初始值
for(int i=1;i<=len;i++)//处理进位
a[i+1]+=a[i]/10,a[i]%=10;
while(!a[len])//将多余的长度去掉
len--;
}
3.重载高精度加法
回顾一下我们小学学的竖式加法。套用模拟问题的思路,我们就可以实现高精度加法。
//为了方便演示,这里定义成友元函数,暂时不需要知道为什么,也可以自己上网搜一下
friend bigint operator+(bigint a,bigint b)//重载高精度加法
{
bigint c;//存储最终的答案
int _len=max(a.len,b.len);//累加
for(int i=1;i<=_len;i++)//循环累加
c[i]+=a[i]+b[i];
c.flatten(_len+1);//计算后最多不超过_len+1位
return c;
}
4.重载高精度乘法
跟高精度加法一样,高精度乘法也是通过模拟乘法竖式实现的。但直接寻找规律有一些复杂。我们先不处理进位,将乘法数竖式列成表格的形式观察。
数 | 第6位 | 第5位 | 第4位 | 第3位 | 第2位 | 第1位 |
---|---|---|---|---|---|---|
a | 5 | 1 | 4 | |||
b | 4 | 9 | 5 | |||
a*b[1] | 25 | 5 | 20 | |||
a*b[2] | 45 | 9 | 36 | |||
a*b[3] | 20 | 4 | 16 | |||
中间产物 | 20 | 49 | 50 | 41 | 20 | |
处理进位 | 2,进0 | 25,进2 | 54,进5 | 54,进5 | 43,进4 | 20,进2 |
结果 | 2 | 5 | 4 | 4 | 3 | 0 |
通过观察可以发现,第一个数的第i位乘第二个数的第j位的值贡献给了答案的i+j-1
位。因此,我们可以先计算贡献,最后处理进位。
上表借鉴了深入浅出入门篇,请见谅
friend bigint operator*(bigint a,bigint b)//重载高精度乘法
{
bigint c;//储存答案
int lena=a.len,lenb=b.len;
for(int i=1;i<=lena;i++)//遍历a的每一位
for(int j=1;j<=lenb;j++)//遍历b的每一位
c[i+j-1]+=a[i]*b[j];//计算贡献
c.flatten(lena+lenb);//答案长度不会超过两数长度之和
return c;
}
5.完整代码
#define MAXN 1010
struct bigint
{
int len,a[MAXN];
bigint(int x=0)//默认这个数是0
{
memset(a,0,sizeof(a));//初始化数组a
if(x==0)//如果x是0会直接跳过底下的循环,导致len被初始化为0,所以要特判
{
len=1;
return;//直接返回
}
for(len=1;x;len++)//只要x没被取完就一直去并增加这个数的长度
a[len]=x%10,x/=10;//倒序存储x
len--;//最后len总会多1
}
int &operator[](int i)//用x[i]代替x.a[i]
{
return a[i];
}
void in_data()//输入高精度整数
{
string s;cin>>s;//暂时存储到字符串里面
memset(a,0,sizeof(a));len=0;//清空数组并初始化这个大整数
for(int i=s.length()-1;i>=0;i--,len++)//字符串转整数
a[s.length()-i]=s[i]-'0';
}
void print()//打印这个数字,注意数组a是倒着存储各个数位的
{
//注意在后面的处理中可能会出现len=0的情况,所以i的初始值要写成max(num.len, 1)
for(int i=max(len,1);i>=1;i--)
printf("%d",a[i]);//前面重载过[]了,等价于num.a[i]
}
void flatten(int L)//处理1到L范围内的进位并重置长度,需要保证L不小于有效长度
{
len=L;//先赋一个初始值
for(int i=1;i<=len;i++)//处理进位
a[i+1]+=a[i]/10,a[i]%=10;
while(!a[len])//将多余的长度去掉
len--;
}
friend bigint operator+(bigint a,bigint b)//重载高精度加法
{
bigint c;//存储最终的答案
int _len=max(a.len,b.len);//累加
for(int i=1;i<=_len;i++)//循环累加
c[i]+=a[i]+b[i];
c.flatten(_len+1);//计算后最多不超过_len+1位
return c;
}
friend bigint operator*(bigint a,bigint b)//重载高精度乘法
{
bigint c;//储存答案
int lena=a.len,lenb=b.len;
for(int i=1;i<=lena;i++)//遍历a的每一位
for(int j=1;j<=lenb;j++)//遍历b的每一位
c[i+j-1]+=a[i]*b[j];//计算贡献
c.flatten(lena+lenb);//答案长度不会超过两数长度之和
return c;
}
};