文章目录
- 高精度运算
- 高精度加法
- 高精度减法
- 高精度乘法
- 高精度除法
- 前缀和
- 二维前缀和
- 差分
- 二维差分
- 高精度练习题
- 791. 高精度加法
- 792. 高精度减法
- 793. 高精度乘法
- 794. 高精度除法
- 前缀和练习题
- 795. 前缀和
- 796. 子矩阵的和
- 差分练习题
- 797. 差分
- 798. 差分矩阵
高精度运算
两个大数做运算,位数一般是1e6
大整数的存储,数组的低位存储整数的低位,类似于小端存储
高精度加法
Ai + Bi + t
:每一位相加,再加上进位,一开始进位为0
用t
变量保存Ai
和Bi
相加的结果,不过需要先判断这两个数是否存在,一开始t
为0
当A
和B
的每一位数都加完后,还判断是否还有进位,即t
是否为1
,若有进位,最高位需要补上1
// C = A + B
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 /= 10;
}
if (t) C.push_back(1);
return C;
}
另外一种思路,默认将A
作为更长的大整数,也就是当A
的长度小于B
的程度时,执行add(B, A)
// C = A + B
vector<int> add(vector<int>& A, vector<int>& B)
{
vector<int> C;
int t = 0; // 进位
if (A.size() < B.size()) return add(B, A);
for (int i = 0; i < A.size(); ++i)
{
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;
}
高精度减法
Ai - Bi - t
:从最低位开始,每一位相减,再减去借位
若以上的值小于0,那么计算的结果为Ai - Bi - t + 10
若以上的值大于等于0,那么计算的结果为Ai - Bi - t
将以上的结果保存到t
中,也就是每次计算的时候:t = A[i] - B[i] - t
对于最后的结果,可以用(t + 10) % 10
处理:将t小于0以及t大于等于0的两种情况合并到一起
以上计算的前提是:A
大于B
。若A
小于B
, 则需要计算B - A
。即保证被减数的绝对值大于减数
所以在进行计算前,需要比较两个数的大小:
- 先比较长度,长度长的数更大
- 若两数长度一样,那么从高位开始比较
- 若 A i A_i Ai != B i B_i Bi,则返回 A i A_i Ai > B i B_i Bi这个表达式的结果
- 若循环走完,函数还没有返回,那么返回
true
,表示A
与B
相等
以下模板只给出了两个正数相减的情况。若数字存在负数,此时可以转换为两数的绝对值相减或者相加,关于最后的符号问题,分情况讨论即可
需要注意的是:最后需要去除前导0
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
vector<int> sub(vector<int>& A, vector<int>& B)
{
int t = 0;
vector<int> C;
for (int i = 0; i <= A.size(); ++i)
{
t = A[i] - t;
if (i < B.size()) t -= B[i];
C.push_back((t + 10) % 10);
if (t < 0) t = 1;
else t = 0;
}
// 去除前导0
while (C.size() > 1 && C.back() == 0) C.pop_back();
return C;
}
高精度乘法
注意:只有一个数是高精度的,另一个数比较小
A * b
,len(A) <= 10
,b <= 10000
将b
看成一个整体和A
相乘,而不是A
和b
一位一位的相乘
用t
保存每一次b
和
A
i
A_i
Ai相乘的结果,同时加上进位,注意t
同时也是进位,且t
的初值为0
即t += A[i] * b
,t / 10
为下一次计算的进位,t % 10
为C
需要保存的结果,保存的顺序从低位开始到高位
当
A
n
A_n
An的每一位和b
相乘完后,可能还留有进位,即t
不为0,此时仍然需要对t
进行% 10 / 10
的操作,直到t
为0
可以通过以下具体例子,推演高精度乘法的过程
// C = A * b
vector<int> mul(vector<int>& A, int b)
{
vector<int> C;
int t = 0; // 进位
for (int 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;
}
注意:乘法运算结果,也需要去除前导0
!
高精度除法
同样,A
为高精度,而b
是较小的数
- 从最高位
A
i
A_i
Ai开始,此时余数为
0
,将余数乘以10
,加上 A i A_i Ai,作为新的被除数 - 将被除数除以
b
,商作为结果的第一位(最高位 ),保留被除数模b
的余数 - 将余数乘以
10
,加上 A i − 1 A_{i-1} Ai−1。得到新的被除数,商作为结果的第二位,保留除数模b
的余数… - 直到走到
A
的最低位,此时的余数为整个结果的余数
可以发现,余数和被除数之间存在着一种转换关系,这里用r
表示余数与被除数
由于我们使用数组的低位保存大整数的低位,而除法运算中,我们每次得到的是结果的高位。并且整数的高位到数组的低位上,所以最后需要反转字符串。同时,需要去除前导0
// C = A / b
vector<int> div(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]; // 得到被除数
C.push_back(r / b);
r %= b; // 得到余数
}
reverse(C.begin(), C.end());
while (C.size() > 1 && C.back() == 0) C.pop_back();
return C;
}
前缀和
数组 a n a_n an,前缀和 S i S_i Si就是累加 a 1 a_1 a1到 a i a_i ai
如何求Si?
公式:
S
i
S_i
Si =
S
i
−
1
S_{i-1}
Si−1 +
a
i
a_i
ai
作用?
快速的求出原数组中某一区间的和,sum[l, r] =
S
r
S_r
Sr -
S
l
−
1
S_{l-1}
Sl−1
二维前缀和
公式: S i j S_{ij} Sij = a i j a_{ij} aij + S i − 1 , j S_{i-1,j} Si−1,j + S i , j − 1 S_{i,j-1} Si,j−1 - S i − 1 , j − 1 S_{i-1,j-1} Si−1,j−1
差分
给定数组
a
n
a_n
an,构造
b
n
b_n
bn数组使得
a
n
a_n
an数组是
b
n
b_n
bn数组的前缀和
b
1
b_1
b1 =
a
1
a_1
a1 -
a
0
a_0
a0
b
2
b_2
b2 =
a
2
a_2
a2 -
a
1
a_1
a1
b
n
b_n
bn =
a
n
a_n
an -
a
n
−
1
a_{n-1}
an−1,注意
a
0
a_0
a0的值为0,这是假设的
但是构建差分数组不使用以上方法,以上只是差分数组的性质。构建差分数组则是利用这一性质:
若现在有操作需要对[ a l a_l al, a r a_r ar]中的所有数加上c。此时可以不对 a n a_n an进行操作,而对其差分数组 b n b_n bn进行操作
因为 a n a_n an是 b n b_n bn的前缀和数组,若 b l b_l bl + c,那么 a l a_l al + c, a l + 1 a_{l+1} al+1 + c, a r a_r ar + c。因为 a r + 1 a_{r+1} ar+1不需要+c。因此 b r + 1 b_{r+1} br+1 - c,使得 a r + 1 a_{r+1} ar+1不变。这样操作就只用O(1)的时间完成了需要对 a n a_n an数组的O(n)操作
差分数组的构造,假定原矩阵
a
n
a_n
an元素全为0,然后将
a
i
a_i
ai插入到区间[i, i]
中,此时修改
b
i
b_i
bi为
b
i
b_i
bi +
a
i
a_i
ai,修改
b
i
+
1
b_{i+1}
bi+1为
b
i
+
1
b_{i+1}
bi+1 -
a
i
a_i
ai
对于差分,不需要考虑如何构造,只需要考虑如何更新即可。以上方法是一种特殊的更新,可以用于差分的构造
差分模板:
// 核心是insert操作,对原数组的某段区间加上某个值
void insert(int l, int r, int c)
{
b[l] += c;
b[r + 1] -= c;
}
至于原数组 a n a_n an怎么求,对差分数组 b n b_n bn用前缀和公式即可
二维差分
同样,构造即更新,所以先理解更新:
若给定一个二维矩阵,矩阵中i
行j
列的元素用
a
i
j
a_{ij}
aij表示,现需要对矩阵中以[
x
1
x_1
x1,
y
1
y_1
y1]为左上角,以[
x
2
x_2
x2,
y
2
y_2
y2]为右下角中的所有元素加上某个值,要怎么做?
根据
a
i
j
a_{ij}
aij矩阵构造其差分矩阵
b
i
j
b_{ij}
bij,其中
a
i
j
a_{ij}
aij矩阵为
b
i
j
b_{ij}
bij矩阵的前缀和。
当
b
x
1
,
y
1
b_{x_1, y_1}
bx1,y1 + c时,
a
i
j
a_{ij}
aij矩阵中,满足i >=
x
1
x_1
x1 && j >=
y
1
y_1
y1的所有元素都变成:
a
i
,
j
a_{i, j}
ai,j + c。
而需要+ c的区间为[
x
1
x_1
x1,
y
1
y_1
y1]到[
x
2
x_2
x2,
y
2
y_2
y2],所以对于满足i >=
x
2
x_2
x2 || j >=
y
2
y_2
y2的元素,需要保持原样,现在它们的值为
a
i
,
j
a_{i, j}
ai,j + c,需要修改为
a
i
,
j
a_{i, j}
ai,j + c - c
此时需要修改差分数组,
b
x
2
+
1
,
y
1
b_{x_2+1, y_1}
bx2+1,y1 - c,
b
x
1
,
y
2
+
1
b_{{x_1}, y_2+1}
bx1,y2+1 - c,
b
x
2
+
1
,
y
2
+
1
b_{x_2+1, y_2+1}
bx2+1,y2+1 + c
此时该差分矩阵推导出的原矩阵中,以[
x
1
x_1
x1,
y
1
y_1
y1]为左上角,以[
x
2
x_2
x2,
y
2
y_2
y2]为右下角中的所有元素都 + c
二维差分模板:
void insert(int x1, int y1, int x2, int y2, int c)
{
b[x1][y1] += c;
b[x2 + 1][y1] -= c;
b[x1][y2 + 1] -= c;
b[x2 + 1][y2 + 1] += c;
}
高精度练习题
791. 高精度加法
791. 高精度加法 - AcWing题库
输入的处理:用string
获取两个加数A
和B
,将加数的低位放到数组的低下标处,高位放到数组的高下标处
用高精度加法模板进行加法运算
#include <iostream>
#include <string>
#include <vector>
using namespace std;
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 /= 10;
}
if (t) C.push_back(1);
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');
vector<int> C = add(A, B);
for (int i = C.size() - 1; i >= 0; --i) printf("%d", C[i]);
return 0;
}
792. 高精度减法
792. 高精度减法 - AcWing题库
我认为cmp
的判断写得很巧妙,两个判断都是当不等于的时候,返回一个比较的结果
这样子处理的逻辑也更清晰
以及,当A < B
时,为了不写花括号,y总甚至写了逗号表达式,怎么说,这样子处理也是不错的吧
#include <iostream>
#include <string>
#include <vector>
using namespace std;
string a, b;
vector<int> A, B;
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
vector<int> sub(vector<int>& A, vector<int>& B)
{
int t = 0;
vector<int> C;
for (int i = 0; i <= A.size(); ++i)
{
t = A[i] - t;
if (i < B.size()) t -= B[i];
C.push_back((t + 10) % 10);
if (t < 0) t = 1;
else t = 0;
}
// 去除前导0
while (C.size() > 1 && C.back() == 0) C.pop_back();
return C;
}
int main()
{
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');
vector<int> C;
if(cmp(A, B)) C = sub(A, B);
else C = sub(B, A), printf("-");
for (int i = C.size() - 1; i >= 0; --i) printf("%d", C[i]);
return 0;
}
793. 高精度乘法
793. 高精度乘法 - AcWing题库
#include <iostream>
#include <string>
#include <vector>
using namespace std;
string a;
vector<int> A;
int b;
vector<int> mul(vector<int>& A, int b)
{
int t = 0;
vector<int> C;
for (int 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()
{
cin >> a >> b;
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;
}
794. 高精度除法
794. 高精度除法 - AcWing题库
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
vector<int> A;
string a;
int b, r;
vector<int> div(vector<int>& A, int b, int& r)
{
r = 0;
vector<int> C;
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()
{
cin >> a >> b;
for (int i = a.size() - 1; i >= 0; --i) A.push_back(a[i] - '0');
auto C = div(A, b, r);
for (int i = C.size() - 1; i >= 0; --i) printf("%d", C[i]);
printf("\n%d", r);
return 0;
}
前缀和练习题
795. 前缀和
795. 前缀和 - AcWing题库
给定一个数组,获取该数组后,根据其询问的区间返回区间和
利用前缀和数组的预处理,从
a
n
a_n
an数组得到
S
n
S_n
Sn前缀和数组
当询问[l, r]
的区间和时,只要返回
S
r
S_r
Sr -
S
l
−
1
S_{l-1}
Sl−1
#include <iostream>
using namespace std;
const int N = 1e6 + 10;
int a[N] = {0}, S[N] = {0};
int n,m;
int l,r;
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
for (int i = 1; i <= n; ++i) S[i] = S[i - 1] + a[i];
while (m--)
{
scanf("%d%d", &l, &r);
printf("%d\n", S[r] - S[l - 1]);
}
return 0;
}
796. 子矩阵的和
796. 子矩阵的和 - AcWing题库
利用二维前缀和的公式即可解题
#include <iostream>
using namespace std;
const int N = 1010;
int a[N][N] = {0}, S[N][N] = {0};
int n, m, q;
int x1, y1, x2, y2;
int main()
{
scanf("%d%d%d", &n, &m, &q);
// 获取原矩阵
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j)
scanf("%d", &a[i][j]);
// 构建前缀和矩阵
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j)
S[i][j] = a[i][j] + S[i - 1][j] + S[i][j - 1] -S[i - 1][j - 1];
// 处理询问
while (q--)
{
scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
printf("%d\n", S[x2][y2] - S[x2][y1 - 1] - S[x1 - 1][y2] + S[x1 - 1][y1 - 1]);
}
return 0;
}
差分练习题
797. 差分
797. 差分 - AcWing题库
题目输入原数组 a n a_n an,我们用特殊的更新构建其差分数组 b n b_n bn。对于原数组的区间操作,只要修改差分数组即可,最后根据差分数组使用前缀和公式推导原数组即可
#include <iostream>
using namespace std;
const int N = 1e6 + 10;
int a[N], b[N] = {0};
int n, m;
int l, r, c;
void insert(int l, int r, int c)
{
b[l] += c;
b[r + 1] -= c;
}
int main()
{
scanf("%d%d", &n, &m);
// 获取a数组
for (int i = 0; i < n; ++i) scanf("%d", &a[i]);
// 构造其差分数组
for (int i = 0; i < n; ++i) insert(i, i, a[i]);
while (m--)
{
scanf("%d%d%d", &l, &r, &c);
insert(l - 1, r - 1, c);
}
// 推导a数组
for (int i = 1; i < n; ++i) b[i] += b[i - 1];
// 打印最终数组
for (int i = 0; i < n; ++i) printf("%d ", b[i]);
return 0;
}
所以还可以这样解:
#include <iostream>
using namespace std;
const int N = 1e6 + 10;
int a[N] = {0}, b[N] = {0};
int n, m;
int l, r, c;
void insert(int l, int r, int c)
{
b[l] += c;
b[r + 1] -= c;
}
int main()
{
scanf("%d%d", &n, &m);
// 获取a数组
for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
// 构造其差分数组
for (int i = 1; i <= n; ++i) insert(i, i, a[i]);
while (m--)
{
scanf("%d%d%d", &l, &r, &c);
insert(l, r, c);
}
// 推导a数组
for (int i = 1; i <= n; ++i) a[i] = (b[i] += b[i - 1]);
// 打印最终数组
for (int i = 1; i <= n; ++i) printf("%d ", a[i]);
return 0;
}
其实可以不用创建 a n a_n an数组,直接使用 S n S_n Sn数组即可
#include <iostream>
using namespace std;
const int N = 1010;
int S[N][N] = {0};
int n, m, q;
int x1, y1, x2, y2;
int main()
{
scanf("%d%d%d", &n, &m, &q);
// 获取原矩阵
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j)
scanf("%d", &S[i][j]);
// 构建前缀和矩阵
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j)
S[i][j] += (S[i - 1][j] + S[i][j - 1] -S[i - 1][j - 1]);
// 处理询问
while (q--)
{
scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
printf("%d\n", S[x2][y2] - S[x2][y1 - 1] - S[x1 - 1][y2] + S[x1 - 1][y1 - 1]);
}
return 0;
}
798. 差分矩阵
798. 差分矩阵 - AcWing题库
#include <iostream>
using namespace std;
const int N = 1010;
int n,m,q;
int x1, y1, x2, y2, c;
int a[N][N] = {0}, b[N][N] = {0};
void insert(int x1, int y1, int x2, int y2, int c)
{
b[x1][y1] += c;
b[x2 + 1][y1] -= c;
b[x1][y2 + 1] -= c;
b[x2 + 1][y2 + 1] += c;
}
int main()
{
scanf("%d%d%d", &n, &m, &q);
// 获取a矩阵
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j)
scanf("%d", &a[i][j]);
// 构建其差分矩阵
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j)
insert(i, j, i, j, a[i][j]);
// 根据请求修改差分矩阵
while (q--)
{
scanf("%d%d%d%d%d", &x1, &y1, &x2, &y2, &c);
insert(x1, y1, x2, y2, c);
}
// 根据差分矩阵与前缀和公式推导原矩阵
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j)
b[i][j] += (b[i -1][j] + b[i][j - 1] - b[i - 1][j - 1]);
// 输出原矩阵
for (int i = 1; i <= n; ++i)
{
for (int j = 1; j <= m; ++j)
printf("%d ", b[i][j]);
printf("\n");
}
return 0;
}
这里最好将差分数组以及原数组的i = 0
以及j = 0
的位置初始化为0
并且不使用,从下标为1
的位置开始使用数组。因为最后推导原数组中,需要用到这些下标为0
的元素