前缀和 差分数组
当我们有一连串的数字的时候 我们要求这串数字中某一区间数字的合,首先想到的办法是找到这个区间的开头和结尾,但是要求多组的这样区间的和的时候,这样的“笨办法将会非常的麻烦”,占时间也占空间。
于是我们可以引入一个概念:前缀和
前缀和是某下标之前包括这个下标的数组内数字的和
a[]为原数组,b[]为前缀和数组
则可得b[]的递推式为:
b[i]=b[i-1]+a[i]
说简单就是有一个数组,它每一位是包括当前这一位与之前所有位的和。
举个栗子
a[0] a[1] a[2] a[3] a[4] a[5]
1 2 3 4 5 6
b[0] b[1] b[2] b[3] b[4] b[5]
1 3 6 10 15 21
其中b[0]=a[0]
b[1]=b[0]+a[1]
b[2]=b[1]+a[2]
…
前缀和的作用
可以通过一次的运算求出这个数组中一个区间的和
比如我们要求出a[1]到a[4]的和(包括这两位)时可以选择用一个循环从1遍历到4依次相加,结果为14
如果用前缀和只需要计算出 b[4] -b[0]=14的结果就好了
由此我们可以得出当要求某个区间 i 到 j 的所有数字之和时,只需计算这个数组的前缀和数组
ans=b[j]-b[i-1]
大大简化了操作,看了没懂的可以试试手推。
这个东西用法也蛮多的,我这里就讲讲思想就好了,再难的我也说不出来了。
差分数组
为啥把这两个东西放在一块说呢,因为它俩貌似是个逆运算。
差分数组的用途其实是修改一个区间内的所有值
咋算嘞?
我们假设a[]为原始数组b[]为差分数组则:
b[0]=a[0]
b[i]=a[i]-a[i-1]
说白了就是原来数组相邻两项的差
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | |
a[] | 1 | 3 | 4 | 5 | 7 | 8 | 9 | 11 |
b[] | 1 | 2 | 1 | 1 | 2 | 1 | 1 | 2 |
假设我们要在[2,5]这个区间加上5
在[1,6]这个区间上减3
我们可以通过循环来执行
可是当数据过多,区间过大的时候,这个操作也将会非常耗时耗力耗内存。
然后差分数组他就闪亮的的登场了!!!
差分有个神奇的地方,就是当你给原来的数组区间加上或减去同一个数字的时候,他们的差分是不会变的
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | |
a[] | 1 | 3 | 4+5 | 5+5 | 7+5 | 8+5 | 9 | 11 |
b[] | 1 | 2 | 1 | 1 | 2 | 1 | 1 | 2 |
所以如果我们要修改这个区间的值的时候,就可以通过修改他们的差分数组来修改
假设我们要给[L,R]区间上加x,由于在这个区间上加的数字是相同的,所以在这个区间的差分是不变的,只需要修改两个短点的值就好了。
b[L]=a[L]-a[L-1]
=>a[L]=b[L]+a[L-1]
所以要让 a[L] 加上x 就给b[L]加上 x 就好了
因为:
a[i]=b[i]+a[i-1]
=b[i]+b[i-1]+a[i-2].....
以此类推,但是给b[L]加了 x 后 L 之后的每个a[i] 都会变大 x
所以在区间的右端要减去 x 不让区间外的数字改变大小,但是a[R]也要改变,所以只能在b[R+1]的地方减去 x
总结来说就是当要改变区间[L,R]中数字的大小的时候,只需要对它的差分数组中的
{b[L]+x,b[R+1]-x}
来一道差分组的题
#include <cstring>
#include <iostream>
using namespace std;
int a[100009];
int b[100009];
int main() {
int N;
while (cin >> N && N != 0) {
memset(a, 0, sizeof(a));
memset(b, 0, sizeof(b));
int L, R;
for (int i = 1; i <= N; i++) { //对区间内的每个都加 1
cin >> L >> R;
b[L] += 1;
b[R + 1] -= 1;
}
for (int i = 1; i <= N; i++) { //转换为原数组
a[i] = a[i - 1] + b[i];
}
for (int i = 1; i <= N; i++) {
if (i != 1) cout << " ";
cout << a[i];
}
cout << endl;
}
return 0;
}