两个大数做运算,位数一般是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
。即保证被减数的绝对值大于减数
所以在进行计算前,需要比较两个数的大小:
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
是较小的数
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;
}
输入的处理:用string
获取两个加数A
和B
,将加数的低位放到数组的低下标处,高位放到数组的高下标处
用高精度加法模板进行加法运算
#include
#include
#include
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;
}
我认为cmp
的判断写得很巧妙,两个判断都是当不等于的时候,返回一个比较的结果
这样子处理的逻辑也更清晰
以及,当A < B
时,为了不写花括号,y总甚至写了逗号表达式,怎么说,这样子处理也是不错的吧
#include
#include
#include
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;
}
#include
#include
#include
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;
}
#include
#include
#include
#include
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. 前缀和 - 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
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;
}
利用二维前缀和的公式即可解题
#include
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;
}
题目输入原数组 a n a_n an,我们用特殊的更新构建其差分数组 b n b_n bn。对于原数组的区间操作,只要修改差分数组即可,最后根据差分数组使用前缀和公式推导原数组即可
#include
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
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
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;
}
#include
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
的元素