前缀和
-
对于一个序列 a a a,它的“前缀和”序列 s s s可以通过递推计算
s [ i ] = ∑ j = 1 i a [ j ] = s [ i − 1 ] + a [ i ] s[i] = \sum\limits_ {j = 1} ^ {i} a[j] = s[i - 1] + a[i] s[i]=j=1∑ia[j]=s[i−1]+a[i] -
区间和,即序列 a a a在 [ l , r ] [l,r] [l,r]之间的数之和,可以通过“前缀和”相减求得
∑ i = l r a [ i ] = s [ r ] − s [ l − 1 ] \sum\limits_ {i = l} ^ {r} a[i] = s[r] - s[l - 1] i=l∑ra[i]=s[r]−s[l−1]
二维前缀和
- 在二维数组中,二维前缀和如下:
s [ i , j ] = ∑ x = 1 i ∑ y = 1 j a [ x , y ] = s [ i − 1 , j ] + s [ i , j − 1 ] − s [ i − 1 , j − 1 ] + a [ i , j ] s[i, j] = \sum\limits_ {x = 1} ^ {i} \sum\limits_ {y = 1} ^ {j} a[x, y] \\ = s[i - 1, j] + s[i, j - 1] - s[i - 1, j - 1] + a[i, j] s[i,j]=x=1∑iy=1∑ja[x,y]=s[i−1,j]+s[i,j−1]−s[i−1,j−1]+a[i,j] - 区间和,以边长为
r
r
r的正方形为例:
∑ x = i − r + 1 i ∑ y = j − r + 1 j a [ x , y ] = s [ i , j ] − s [ i − r , j ] − s [ i , j − r ] + s [ i − r , j − r ] \sum\limits_{x = i - r + 1} ^ {i} \sum\limits_ {y = j - r + 1} ^ {j} a[x, y] \\ = s[i, j] - s[i - r, j] - s[i, j - r] + s[i - r, j - r] x=i−r+1∑iy=j−r+1∑ja[x,y]=s[i,j]−s[i−r,j]−s[i,j−r]+s[i−r,j−r] - O ( N 2 ) O(N^2) O(N2)递推求出二维前缀和, O ( N 2 ) O(N^2) O(N2)枚举边长为 r r r的正方形的右下角坐标 ( i , j ) (i, j) (i,j),即可 O ( 1 ) O(1) O(1)计算正方形内数字之和。
差分
- 对于一个序列
a
a
a,定义基于
a
a
a的差分序列
b
b
b:
b [ 1 ] = a [ 1 ] b [ i ] = a [ i ] − a [ i − 1 ] ( 2 ≤ i ≤ n ) b[1] = a[1] \\ b[i] = a[i] - a[i - 1] (2 \le i \le n) b[1]=a[1]b[i]=a[i]−a[i−1] (2≤i≤n) - 前缀和与差分是互逆运算,差分序列的前缀和序列即为原序列 a a a,而前缀和序列的差分序列也是原序列 a a a。
- 把区间 a [ l . . . r ] a[l ... r] a[l...r]中的每一项加上 d d d(区间修改),只要将其差分序列 b [ l ] + d b[l] + d b[l]+d, b [ r + 1 ] − d b[r + 1] - d b[r+1]−d,其它元素不变,即将“区间修改”,变为“单点修改”。
ST算法
- 当频繁进行区间最值查询( R M Q RMQ RMQ)时, S T ST ST算法的时间复杂度是 O ( 1 ) O(1) O(1),预处理 S T ST ST表( s p a r s e sparse sparse t a b l e table table)的时间复杂度是 O ( n ∗ l o g n ) O(n*logn) O(n∗logn)。
- 使用该算法时,不支持对原序列进行修改。
- 当我们要查询区间
a
[
l
.
.
.
r
]
a[l...r]
a[l...r]的最大值时,可以把区间分成左右两个子集,每个子集的最大值中更大的那个,就是区间
a
[
l
.
.
.
r
]
a[l...r]
a[l...r]的最大值。即使两个子集有交集,得到的结果仍然是我们想要的。这个性质适用于求最大值和最小值,但不适用于求和运算。
- 每次二分的区间长度均为
2
2
2的幂次方,这样便于不断拆分,直至区间内剩下唯一的一个元素。我们用
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示区间
a
[
i
.
.
.
i
+
2
j
−
1
]
a[i ... i + 2^j - 1]
a[i...i+2j−1]的最大值,当区间内只有一个元素
a
[
i
]
a[i]
a[i]时,
f
[
i
]
[
0
]
f[i][0]
f[i][0]就等于
a
[
i
]
a[i]
a[i]。这样一来,区间
a
[
l
.
.
.
r
]
a[l ... r]
a[l...r]的最大值,就可以通过以下方式进行计算:
m a x x = m a x ( f [ l ] [ k ] , f [ r − 2 k + 1 ] [ k ] ) maxx = max(f[l][k], f[r - 2^k + 1][k]) maxx=max(f[l][k],f[r−2k+1][k]) - 如何预处理
f
f
f数组呢?可以采用递推的方式。只要已知
f
[
i
]
[
j
−
1
]
f[i][j - 1]
f[i][j−1]和
f
[
i
+
2
j
−
1
]
[
j
−
1
]
f[i + 2 ^ {j - 1}][j - 1]
f[i+2j−1][j−1]的大小,就可以推算出
f
[
i
]
[
j
]
f[i][j]
f[i][j],即区间
a
[
i
.
.
.
i
+
2
j
−
1
]
的
最
大
值
a[i ... i +2^j - 1]的最大值
a[i...i+2j−1]的最大值
f [ i ] [ j ] = m a x ( f [ i ] [ j − 1 ] , f [ i + 2 j − 1 ] [ j − 1 ] ) f[i][j] = max(f[i][j - 1], f[i + 2 ^ {j - 1}][j - 1]) f[i][j]=max(f[i][j−1],f[i+2j−1][j−1]) - 模板题,数列区间最大值 http://ybt.ssoier.cn:8088/problem_show.php?pid=1541
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n, m, a[N], f[N][20];
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++) {
scanf("%d", &a[i]);
f[i][0] = a[i];
}
int t = log2(n);
for (int j = 1; j <= t; j ++) {
for (int i = 1; i + (1 << j) - 1 <= n; i ++) {
int k = j - 1;
f[i][j] = max(f[i][k], f[i + (1 << k)][k]);
}
}
while (m --) {
int x, y;
scanf("%d%d", &x, &y);
int k = log2(y - x + 1); //分成2组,每一组元素个数为2^k个
int maxx = max(f[x][k], f[y + 1 - (1 << k)][k]);
printf("%d\n", maxx);
}
return 0;
}
树状数组
- 我们思考“单点修改,区间查询”的问题,即修改a序列的某个元素,查询a序列某个区间的和的问题。如果用朴素的方法,修改的时间复杂度是 O ( 1 ) O(1) O(1),而累加求和的时间复杂度是 O ( n ) O(n) O(n);如果用前缀和,虽然可以做到 O ( 1 ) O(1) O(1)查询区间和,但某个元素更改后,需要 O ( n ) O(n) O(n)更新前缀和序列,鱼和熊掌不可兼得。
- 如何平衡“修改”和“查询”这两个操作的时间复杂度?接下来的方法用 O ( l o g n ) O(logn) O(logn)的时间复杂度进行修改和查询。
- 举例说明查询区间和的方法:
– 由于 12 = 8 + 4 12 = 8 + 4 12=8+4,我们可以把 a [ 1...12 ] a[1...12] a[1...12]分成 a [ 1...8 ] a[1...8] a[1...8]、 a [ 9...12 ] a[9...12] a[9...12]两组
– 由于 11 = 8 + 2 + 1 11 = 8 + 2 + 1 11=8+2+1,我们可以把 a [ 1...11 ] a[1...11] a[1...11]分成 a [ 1...8 ] a[1...8] a[1...8]、 a [ 9...10 ] a[9...10] a[9...10]、 a [ 11 ] a[11] a[11]三组
– 由于 10 = 8 + 2 10 = 8 + 2 10=8+2,我们可以把 a [ 1...10 ] a[1...10] a[1...10]分成 a [ 1...8 ] a[1...8] a[1...8]、 a [ 9...10 ] a[9...10] a[9...10]两组
– 当我们计算 a [ 1... n ] a[1...n] a[1...n]之和时,我们先把 n n n展开成 2 2 2的幂次方之和,以此将区间 a [ 1... n ] a[1...n] a[1...n]分成几个子区间,每个子区间的长度都是2的幂次方。如果预先处理出每个子区间元素之和,就可以通过不超过 o ( l o g n ) o(logn) o(logn)次加法运算,计算出 a [ 1... n ] a[1...n] a[1...n]之和。
– 如果要计算 a [ l . . . r ] a[l ... r] a[l...r]之和,可以分别计算 a [ 1... r ] a[1...r] a[1...r]和 a [ 1... l − 1 ] a[1...l - 1] a[1...l−1],然后求差 - lowbit函数
– 我们要将 n n n展开成 2 2 2的幂次方之和,每个数字可以通过lowbit函数来求,举例当 n = 11 n = 11 n=11时,
–lowbit(n) = n & -n
,得到1,也就是第一个子区间的长度为 1 1 1。n -= lowbit(n)
,n更新为 10 10 10
–lowbit(n) = n & -n
,得到2,也就是第二个子区间的长度为 2 2 2。n -= lowbit(n)
,n更新为 8 8 8
–lowbit(n) = n & -n
,得到8,也就是第三个子区间的长度为 8 8 8。n -= lowbit(n)
,n更新为 0 0 0
– 循环停止, a [ 1...11 ] a[1...11] a[1...11]一共分为三个区间 - 我们需要预处理出哪项区间和呢?
– a [ 1...1 ] a[1...1] a[1...1]无法再分解,我们用 c [ 1 ] c[1] c[1]表示 a [ 1 ] a[1] a[1]
– a [ 1...2 ] a[1...2] a[1...2]无法再分解,我们用 c [ 2 ] c[2] c[2]表示 a [ 1...2 ] a[1...2] a[1...2]之和
– a [ 1...3 ] a[1...3] a[1...3]分解成 a [ 1...2 ] a[1...2] a[1...2]和 a [ 3 ] a[3] a[3],我们用 c [ 3 ] c[3] c[3]表示 a [ 3 ] a[3] a[3]
– a [ 1...4 ] a[1...4] a[1...4]无法再分解,我们用 c [ 4 ] c[4] c[4]表示 a [ 1...4 ] a[1...4] a[1...4]之和
– a [ 1...5 ] a[1...5] a[1...5]分解成 a [ 1...4 ] a[1...4] a[1...4]和 a [ 5 ] a[5] a[5],我们用 c [ 5 ] c[5] c[5]表示 a [ 5 ] a[5] a[5]
– a [ 1...6 ] a[1...6] a[1...6]分解成 a [ 1...4 ] a[1...4] a[1...4]和 a [ 5...6 ] a[5...6] a[5...6],我们用 c [ 6 ] c[6] c[6]表示 a [ 5...6 ] a[5...6] a[5...6]之和
– a [ 1...7 ] a[1...7] a[1...7]分解成 a [ 1...4 ] a[1...4] a[1...4]、 a [ 5...6 ] a[5...6] a[5...6]、 a [ 7 ] a[7] a[7],我们用 c [ 7 ] c[7] c[7]表示 a [ 7 ] a[7] a[7]
– a [ 1...8 ] a[1...8] a[1...8]无法再分解,我们用 c [ 8 ] c[8] c[8]表示 a [ 1...8 ] a[1...8] a[1...8]之和
–