前缀和与差分
一维前缀和
前缀和可以用于快速计算一个序列的区间和,也有很多问题里不是直接用前缀和,但是借用了前缀和的思想。
用 \(s[i]\) 表示 \(a[0]+a[1]+\dots+a[i]\) 的和(所以称为前缀和),那么要计算区间 \([l, r]\) 的和,也就是计算 \(a[l]+a[l +1]+\dots+a[r]\),只需要计算 \(s[r] - s[l - 1]\) 就可以了,用 \(O(1)\) 的时间就可以计算出来。
#include <iostream>
using namespace std;
const int N = 100010;
int n, m;
int a[N], s[N]; // s[i]保存以i结尾的前缀和
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i ++ ) {
cin >> a[i];
s[i] = s[i - 1] + a[i];
}
while (m -- ) {
int l, r;
cin >> l >> r;
cout << s[r] - s[l - 1] << endl;
}
return 0;
}
差分
差分是前缀和的逆运算,对于一个数组 \(a\),其差分数组 \(b\) 的每一项都是 \(a[i]\) 和前一项 \(a[i - 1]\) 的差,即 \(b[i] = a[i] - a[i - 1]\) 。和前缀和类似的,也是要留出索引是 \(0\) 的位置 \(b[0]=0\),方便计算。
对数组 \(b\) 求前缀和,可以发现就能得到数组 \(a\),所以它保留了数组 \(a\) 的全部信息。而将数组用其差分数组表示的一个优点就是能在 \(O(1)\) 时间里将数组中连续的一段加上一个数 \(c\)。
例如,要将 \(a\) 数组的 \([l, r]\) 全都加上 \(c\)。那么考虑到差分数组 \(b\) 求前缀和的时候就能恢复出 \(a\) 数组,可以发现 \(b[l]\) 前面的数都不用变。而只要将 \(b[l]\) 加上 \(c\),那么 \([l, +\infty]\) 就都加上了 \(c\)(因为从加到 \(b[l]\) 开始就加上了这个 \(c\),后面的所有加出来的 \(a[i]\) 就都比之前多 \(c\) 了)。然后将 \(b[r +1]\) 这个位置减去 \(c\),那么计算前缀和以恢复 \(a\) 的时候,到了 \(a[r + 1]\) 的位置就既没加 \(c\) 也没减 \(c\) 了,也就保证了 \([r + 1, +\infty]\) 是不变的。这样就实现了将 \([l, r]\) 这个区间里的所有数都加上 \(c\)。
#include <iostream>
using namespace std;
const int N = 100010;
int n, m;
int b[N];
void insert(int l, int r, int c)
{
b[l] += c;
b[r + 1] -= c;
}
int main()
{
cin >> n >> m;
int x;
for (int i = 1; i <= n; i ++ ) cin >> x, insert(i, i, x);
while (m -- ) {
int l, r, c;
cin >> l >> r >> c;
insert(l, r, c);
}
for (int i = 1; i <= n; i ++ ) b[i] += b[i - 1];
for (int i = 1; i <= n; i ++ ) cout << b[i] << ' ';
return 0;
}
标签:insert,前缀,int,cin,差分,算法,数组
From: https://www.cnblogs.com/I-am-Sino/p/16970130.html