高级数据结构(1)

前缀和
  • 对于一个序列 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=1ia[j]=s[i1]+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=lra[i]=s[r]s[l1]

二维前缀和
  • 在二维数组中,二维前缀和如下:
    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=1iy=1ja[x,y]=s[i1,j]+s[i,j1]s[i1,j1]+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=ir+1iy=jr+1ja[x,y]=s[i,j]s[ir,j]s[i,jr]+s[ir,jr]
  • 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[i1] (2in)
  • 前缀和与差分是互逆运算,差分序列的前缀和序列即为原序列 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(nlogn)
  • 使用该算法时,不支持对原序列进行修改。
  • 当我们要查询区间 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+2j1]的最大值,当区间内只有一个元素 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[r2k+1][k])
  • 如何预处理 f f f数组呢?可以采用递推的方式。只要已知 f [ i ] [ j − 1 ] f[i][j - 1] f[i][j1] f [ i + 2 j − 1 ] [ j − 1 ] f[i + 2 ^ {j - 1}][j - 1] f[i+2j1][j1]的大小,就可以推算出 f [ i ] [ j ] f[i][j] f[i][j],即区间 a [ i . . . i + 2 j − 1 ] 的 最 大 值 a[i ... i +2^j - 1]的最大值 a[i...i+2j1]
    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][j1],f[i+2j1][j1])
  • 模板题,数列区间最大值 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...l1],然后求差
  • lowbit函数
    – 我们要将 n n n展开成 2 2 2的幂次方之和,每个数字可以通过lowbit函数来求,举例当 n = 11 n = 11 n=11时,
    lowbit(n) = n & -n,得到1,也就是第一个子区间的长度为 1 1 1n -= lowbit(n),n更新为 10 10 10
    lowbit(n) = n & -n,得到2,也就是第二个子区间的长度为 2 2 2n -= lowbit(n),n更新为 8 8 8
    lowbit(n) = n & -n,得到8,也就是第三个子区间的长度为 8 8 8n -= 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]之和
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值