本篇将介绍 前缀和 和 差分 算法。
前缀和
前缀和顾名思义,就是求出前缀的和。让我们从题目入手,当我们遇到下面的题目时:
题目示例
给定N个数,M个询问a,b,要求输出区间[a,b]的区间和。
样例输入:
1 2 3 4 5 6
| 5 3 7 4 9 2 3 2 3 1 4 4 5
|
样例输出:
这是一个标准的一位前缀和模板题 查询区间和,对于这样的题目使用暴力做法则需要O(NM)的时间复杂度。这实在是太慢了,那么我们需要使用前缀和来加快查询的速度。
对于长度为 n 的序列 {ai} 根据下面的递推式构造一个 前缀和数组 S{a1,a1+a2,a1+a2+a3,...}
S0=0,Si=Si−1+ai
当我们需要查询区间和的时候,只需要计算区间两端的差值即可
Sum([l,r])=Sr−Sl−1.
这样,我们使用 O(n) 的时间来预处理后,就可以使用 O(1) 的时间来查询:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| int n,m; vector<int> a; vector<int> p;
void pre() { p = a; for(int i = 1;i <= n;i++) { p[i] += p[i-1]; } }
int query(int l, int r) { return p[r] - p[l-1]; }
|
差分
前缀和对于多次求区间和可以大幅加快速度,但是遇到多次修改区间就不行了,由于每次修改都需要 O(n) 的时间去准备前缀和数组,这实在是太慢了。那么我们就需要换成差分来加快修改。
同前缀和一样,需要维护一个差分数组。对于长度为 n 的序列 {ai} 构建一个 差分数组 D{a1,a2−a1,a3−a2,...} 保存每一位数对于上一位数的差值。
a0=0,Di=ai−ai−1
当我们需要查询 {ai} 时,只需要求 ai=∑j=1iDj 对于差分数组Di,我们可以理解为 将i下标之后的的所有下标的数加上Di
因此,当我们需要将 {ai} 的区间 [l,r] 中的每个数都加上一个 v 的时候,只需要对于差分数组的区间首加上 v ,区间尾的后一个数减去 v 即可。
Dl=Dl+v,Dr−1=Dr−1−v
这样,我们就可以使用 O(1) 的时间来多次修改,使用 O(n) 的时间来查询结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| int n,m; vector<int> a; vector<int> p;
void pre() { p = a; for(int i = 1;i <= n;i++) { p[i] = a[i] - a[i-1]; } }
void add(int l, int r, int v) { p[l] += v; p[r + 1] -= v; }
int query(int n) { int ans; for(int i = 1;i <= n;i++) { ans += p[i]; } return ans; }
|