高精度计算
简介
高精度计算意思就是对一些位数特别多的数(简称大数)进行计算。这些数不能直接用int之类的类型存下来,超出上限了(几十位)。一般就是整数的加减乘除(乘除都是大数和小一些的正常的数进行计算)
下面的都没有考虑负数的情况。
思想
一般就是用一个数组来存储大数,数组的一个元素存储大数的一位,一般是倒着存,也就是数组的第一个元素存个位,第二个元素存十位……因为这样可以方便进行进位(假如存的数是9,加1后就是10,要存储的就从一位变成了两位,如果是正着存的话,需要把所有元素后移一格。倒着存直接在末尾加一个就行了)。
用数组存储后我们再自己手动对每一位进行计算。刚好加减乘都从个位开始,直接正着遍历数组然后计算每一位即可。只有除的时候需要倒着遍历。
代码
加
//加
//这里用引用只是为了避免复制保证速度
vector<int> Add(vector<int>& A, vector<int>& B)
{
vector<int> C; //存储计算的结果
int t = 0; //用于进位的变量
for (int 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会存储需要进位的1,然后下次循环继续加
t /= 10;
}
//如果t不为零说明需要加一位
if (t) C.push_back(1);
return C;
}
减
//减
//这里用引用只是为了避免复制保证速度
//A - B
vector<int> Sub(vector<int>& A, vector<int>& B)
{
vector<int> C; //存储计算的结果
for (int i = 0, t = 0; i < A.size(); i++)
{
t = A[i] - t; //t存储借位后A当前位的值
if (i < B.size()) t -= B[i]; //与B的值相减,假如说减不掉就需要借位,这里就会变成负值
C.push_back((t + 10) % 10); //+10可以确保不管借不借位都可以得到减完了以后的正整数
if (t < 0) t = 1; //负值说明借位了
else t = 0; //没借位
}
//减完以后可能会导致这个大数的前几位变成0,这句是把前导0去掉了
while (C.size() > 1 && C.back() == 0) C.pop_back();
return C;
}
乘
//乘
//这里用引用只是为了避免复制保证速度
//一般用来乘的b用int就可以存下了
vector<int> Mul(vector<int>& A, int b)
{
vector<int> C; //存储计算的结果
int t = 0; //存储进位的结果
for (int i = 0; i < A.size() || t; i++) //t为0了才会停止
{
if (i < A.size()) t += A[i] * b; //上一位乘出来的值加上这一次乘出来的
C.push_back(t % 10); //存储个位
t /= 10; //抛弃个位将值留给下一位
}
return C;
}
除
//除
//这里用引用只是为了避免复制保证速度
// A / b, 商是c,余数是r
vector<int> Mul(vector<int>& A, int b, int &r)
{
vector<int> C;
r = 0;
for (int i = A.size() - 1; i >= 0; i--)
{
r = r * 10 + A[i]; //因为r是上一位留下来的,所以需要*10
C.push_back(r / b); //存入个位
r %= b; //÷完了把余数留下来
}
//因为在上面的循环是正序存入的,所以需要按照大数的格式倒一下
reverse(C.begin(), C.end());
//去掉前导0
while (C.size() > 1 && C.back() == 0) C.pop_back();
return C;
}
前缀和
简介
有的时候,我们需要计算一个数组的中一段连续的数的和,比如有个长度为10的数组,我们需要计算第2到第5个元素的值的和。这时候就需要用到前缀和(基本上也就这里用)
思想
我们用一个新的数组来存储前缀和,每一位存储的是原数组从0开始到当前位置的值的和。这样计算的时候我们就可以直接用左右区间在前缀和数组的两个值相减得到答案。比如前缀和数组第2位存储的就是原数组的第1位加第2位的值。
这样我们再计算上面提到的第2到第5个元素的值的和就可以直接从原数组中取第1个元素和第5个元素相减得到结果(如果取第2个元素的话最终得到的结果会少,因为这个相当于是在消去前几个元素)
代码
//前缀和数组和原数组都从1开始存,N是数组长度。数组长度记得比总位数大1
for(int i = 1; i < N; i++)
s[i] = s[i-1] + A[i]; //s是前缀和数组,A是原数组
int l,r;
int result = s[l] - s[r-1];
补充
假如要算2维的某一位到某一位,就这么算(不写代码了)
假如说这个黑框是我们的整个的二维数组,红框是我们要得到的部分。我们就需要用这个绿框的值减去蓝色阴影的值和黄色阴影的值,再加上一个蓝黄色阴影的值
因为我们的二维的前缀和中每一位存储的值是从这个位的坐标到(0,0)这块面积中所有数的和(存储的公式和下面的求值的公式差不多,s[i,j] = s[i,j-1] + s[i-1,j] - s[i-1, j-1] + a[i,j])
差分
简介
前缀和差分是逆运算,就像积分和微分的关系一样。A是B的前缀和数组,B是A的差分。为了避免混淆,后面统称为原数组和差分数组。
有的时候我们需要在一个数组的一个区间的每一位的值都增加。这时候就可以用到差分了
思想
假如说我们要在[l, r]之间的每一位数的值都增加一个c,我们就可以在差分数组的 l 处 + c,r + 1处 - c。因为原数组是差分数组的前缀和,l处加的c会加到后面的每一位上,r+1处减的c会覆盖l加的c。从而只影响 l 到 r 这个区间的数。
代码
void insert(int l, int r, int c)
{
//b数组就是差分数组
b[l] += c;
b[r] -= c;
}
int main()
{
//a是原数组
//我们在开始的时候令b数组元素全为0
//这一步相当于是给b数组赋值
for (int i = 1; i <= n; i++) insert(i, i, a[i]);
//如果还需要插入则直接用insert
//所有插入完成后,根据差分数组反推原数组(前缀和)
return 0;
}
补充
差分的二维和前缀和的非常类似。假设我们要给红框范围全加上c,那就是给五角星的位置的值加上c,绿框的值也会加上c。所以我们需要给黄色阴影和蓝色阴影的范围减去c,黄蓝色阴影的部分加上c。
存值也就是插入值了,和上面的一样,只是范围比较极端