前缀和
一维前缀和
定义:S[i] = a[1] + a[2] + ... a[i] a[l] + ... + a[r] = S[r] - S[l - 1]
int a[N], S[N];
for (int i = 1; i <= n; i++) S[i] = S[i - 1] + a[i]; // 给定数组a,初始化前缀和数组S
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]) // 非必须
S[i] = S[i - 1] + a[i]; // 未给定数组a,可合并读入和初始化的过程
}
cout << S[r] - S[l - 1] << endl; //区间和的计算,查询
// 计算a[l] + ... + a[r]
//在一些不涉及a[i]的题目中,不必要存储a[i]的值,只需要存储S[i]就足够
一维前缀和的目的就是降低复杂度,计算区间和的复杂度由原本O(n)降低成为了O(1),相当于直接茶查找,不再需要遍历了。
数组a和S的第1个元素都不存储(下标为0),而从第2个元素开始存储(下标为1),因为下标为0的S0=a0我们默认为0,以方便计算。[当计算1至某个数时,计算Sn- S0即可]。
二维前缀和
由以为前缀和,想要快速求出子矩阵的和的时候,就要用上二维前缀和了。
S[i, j] = 第i行j列格子左上部分所有元素的和 以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵的和为: S[x2, y2] - S[x1 - 1, y2] - S[x2, y1 - 1] + S[x1 - 1, y1 - 1]
S[i,j]即为图1红框中所有数的的和为:
S[i,j]=S[i,j−1]+S[i−1,j]−S[i−1,j−1]+a[i,j]S[i,j]=S[i,j−1]+S[i−1,j]−S[i−1,j−1]+a[i,j]
(x1,y1),(x2,y2)这一子矩阵中的所有数之和为:S[x2,y2]−S[x1−1,y2]−S[x2,y1−1]+S[x1−1,y1−1]
//模板
int a[N][N], S[N][N];
// 给定数组a
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] + a[i][j];//可以拆开写
// 没有给定数组a,需要读入并初始化前缀和数组,则可以合并读入和初始化的过程
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) {
scanf("%d", &a[i][j]);
S[i][j] = S[i - 1][j] + S[i][j - 1] - S[i - 1][j - 1] + a[i][j];
}
cout << S[x2][y2] - S[x2][y1 - 1] - S[x1 - 1][y2] + S[x1 - 1][y1 - 1] << endl; // 使用
同样假设数组a中行下标或列下标为0的项都是0,以方便计算。最重要的是复杂度由O(m * n)降为O(1)。[读入数组a和初始化前缀和数组S的过程可以合并在一起].
差分
一维差分
差分就是前缀和的逆运算。
首先给定一个原数组a:a[1], a[2], a[3]....a[n];
然后我们构造一个数组b : b[1] ,b[2] , b[3].... b[i];
使得 a[i] = b[1] + b[2 ]+ b[3] +,,,,,, + b[i] 也就是说,a数组是b数组的前缀和数组,我们就把b数组叫做a数组的差分数组。换句话说,每一个a[i]都是b数组中从头开始的一段区间和。 我们只要有b数组,通过前缀和运算,就可以在O(n) 的时间内得到a数组 。
如何构造差分数组呢,使用最为直接的方法b[i] = a[i] - a[i - 1]
一维差分的作用,也是一个结论:给a数组中的[l,r]区间中的每一个数都加上c,只需对差分数组b做 b[l] + = c, b[r+1] - = c,再求前缀和就行了。时间复杂度为O(1), 大大提高了效率。
代码:
int a[N], B[N];
void insert(int l, int r, int c) {
B[l] += c;
B[r + 1] -= c;
}
// 初始化差分数组
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
insert(i, i, a[i]);
}
// 输出前缀和数组
for (int i = 1; i <= n; i++) {
B[i] += B[i - 1];
printf("%d ", B[i]);
}
二维差分
同样的分体,如果扩展到二维,我们需要让二维数组被选中的子矩阵中的每个元素的值加上c,是否也可以达到O(1)的时间复杂度?当然是可以的,用二维差分,与一维差分很相似,类比二维前缀和、原a数组中a[i][j]是差分数组b数组左上角(1,1)到右下角(i,j)所包围矩形元素的和。
画一个图来理解这个过程:
b[x1][ y1 ] +=c , 让整个a数组中蓝色矩形面积的元素都加上了c。
b[x1,][y2+1]-=c , 让整个a数组中绿色矩形面积的元素再减去c,使其内元素不发生改变。
b[x2+1][y1]- =c ,让整个a数组中紫色矩形面积的元素再减去c,使其内元素不发生改变。
b[x2+1][y2+1]+=c;,对应图4,让整个a数组中红色矩形面积的元素再加上c,红色内的相当于被减了两次,再加上一次c,使其恢复。
这样的操作后再求二维前缀和,就使 矩阵都加c
int B[N][N]; // 二维差分数组
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;
}
// 构造(无需额外的数组a)
int tmp;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
scanf("%d", &tmp);
insert(i, j, i, j, tmp);
}
}
// 转换成二维前缀和数组
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];