浅谈树状数组算法
Part 1 【模版】树状数组 1
- 例题:P3374
题目大意
单点修改,区间查询。
暴力 1
直接模拟题意,时间复杂度 O ( N M ) O(NM) O(NM)。
时间复杂度瓶颈:查询操作。
暴力 2
直接维护前缀和,时间复杂度 O ( N M ) O(NM) O(NM)。
时间复杂度瓶颈:修改操作。
铺垫
考虑这个问题:求十个数使得它们中选取几个数的和可以表示 1 1 1 至 512 512 512 之中的任何一个数。
有一种合法的答案是: 1 , 2 , 4 , 8 , 16 , 32 , 64 , 128 , 256 , 512 1,2,4,8,16,32,64,128,256,512 1,2,4,8,16,32,64,128,256,512。可以观察到这和二进制有很大关系。
多重背包的二进制优化也利用了上述特征,我们能否把它应用到这道题里?可以。
正解思路
考虑在暴力 2 的基础上二进制维护,需要维护使得 s x s_x sx 至 s n s_n sn 增加 k k k,每一次跨度是 i i i 的二进制拆分中最低的一个非零位的位权,简称 lowbit ( i ) \operatorname{lowbit}(i) lowbit(i)。所以每一次维护时间复杂度是 O ( log N ) O(\log N) O(logN)。
查询操作我们也要利用二进制完成,查询 s y s_y sy 和 s x − 1 s_{x-1} sx−1 的值,我们需要从后往前枚举,跨度仍然是 lowbit ( i ) \operatorname{lowbit}(i) lowbit(i),复杂度 O ( log N ) O(\log N) O(logN)。
总复杂度 O ( M ⋅ log N ) O(M\cdot \log N) O(M⋅logN)。
Part 2 【模版】树状数组 2
- 例题:P3368
题目大意
区间修改,单点查询。
暴力 1
直接模拟题意,时间复杂度 O ( N M ) O(NM) O(NM)。
时间复杂度瓶颈:修改操作。
暴力 2
直接维护差分,时间复杂度 O ( N M ) O(NM) O(NM)。
时间复杂度瓶颈:查询操作。
正解思路
注意观察暴力 2,可以转化为上一题,然后利用二进制优化,总时间复杂度也是 O ( M ⋅ log N ) O(M\cdot \log N) O(M⋅logN)。
Part 3 【模版】线段树 1
- 例题:P3372
题目大意
区间修改,区间查询。
暴力思路
模仿第一题思路,如果不考虑时间复杂度就是维护一个前缀和,再维护这个前缀和数组的前缀和,但是时间复杂度是无法承受的。
铺垫
考虑这个数组: 2 , 3 , 1 , 4 , 5 2,3,1,4,5 2,3,1,4,5,我们来求一求它的前缀和以及前缀和的前缀和。
下标 i i i | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
原数 a i a_i ai | 2 | 3 | 1 | 4 | 5 |
前缀和 s i s_i si | 2 | 5 | 6 | 10 | 15 |
前缀和的前缀和 s s i ss_i ssi | 2 | 7 | 13 | 23 | 38 |
可以发现, s s i ss_i ssi 的值相当于 a i a_i ai 的加权前缀和。
正解思路
令 b i b_i bi 表示第 i i i 个数被修改的情况。
式子: s s i = ∑ 1 i a j + ∑ 1 i b j ⋅ ( i + 1 ) + ∑ 1 i b j ⋅ j ss_i=\sum_1^ia_j+\sum_1^ib_j\cdot(i+1)+\sum_1^ib_j\cdot j ssi=∑1iaj+∑1ibj⋅(i+1)+∑1ibj⋅j。
发现我们只需要维护一下原数组的前缀和、修改值的前缀和、修改值的加权前缀和。复杂度同上。
Part 4 逆序对
- 例题:P1908
声明
本题可以使用归并排序,下面介绍一种树状数组的做法。
思路
不妨考虑桶维护。首先对原数组进行离散化,然后正序枚举,计算前面有多少个数比当前数大。这是一个求前缀和的过程,可以用树状数组维护。
总结
树状数组很好用,可以将 O ( N ) O(N) O(N) 的时间复杂度优化到 O ( log N ) O(\log N) O(logN)。