【刷题】数据结构——树状数组

一、简介

树状数组用于两种操作:

  1. 快速求前缀和 O ( l o g n ) O(logn) O(logn)
  2. 修改某一个数 O ( l o g n ) O(logn) O(logn)

这两个操作也可以用其他方法结构完成:

用一个数组存每个数:操作1. O ( n ) O(n) O(n),遍历前n个数求和;操作2. O ( 1 ) O(1) O(1),直接修改即可

维护前缀和一个数组:操作1. O ( 1 ) O(1) O(1);操作2. O ( n ) O(n) O(n)

可见其他结构最坏情况都是 O ( n ) O(n) O(n)

二、算法

已知一个数可以拆成若干个2的幂次之和
x = 2 i 1 + 2 i 2 + 2 i 3 + . . . + 2 i k x=2^{i_1}+2^{i_2}+2^{i_3}+...+2^{i_k} x=2i1+2i2+2i3+...+2ik

其中 i 1 ≤ i 2 ≤ . . . ≤ i k i_1 \leq i_2 \leq ... \leq i_k i1≤i2≤...≤ik

所以区间 ( 0 , x ] (0, x] (0,x]可以拆成

( x − 2 i 1 , x ] (x-2^{i_1},x] (x−2i1,x]
( x − 2 i 1 − 2 i 2 , x − 2 i 1 ] (x-2^{i_1}-2^{i_2},x-2^{i_1}] (x−2i1−2i2,x−2i1]

.

.

.
( x − 2 i 1 − 2 i 2 − . . . − 2 i k , x − 2 i 1 − 2 i 2 − . . . − 2 i k − 1 ] (x-2^{i_1}-2^{i_2}-...-2^{i_k},x-2^{i_1}-2^{i_2}-...-2^{i_{k-1}}] (x−2i1−2i2−...−2ik,x−2i1−2i2−...−2ik−1]

也就是
( 0 , x − 2 i 1 − 2 i 2 − . . . − 2 i k − 1 ] (0,x-2^{i_1}-2^{i_2}-...-2^{i_{k-1}}] (0,x−2i1−2i2−...−2ik−1]

再来看这些区间包含多少个数
( x − 2 i 1 , x ] (x-2^{i_1},x] (x−2i1,x]包含 2 i 1 2^{i_1} 2i1个数,其中 i 1 i_1 i1是 x x x二进制表示的最后一位1的位置
( x − 2 i 1 − 2 i 2 , x − 2 i 1 ] (x-2^{i_1}-2^{i_2},x-2^{i_1}] (x−2i1−2i2,x−2i1]包含 2 i 2 2^{i_2} 2i2个数,其中 i 2 i_2 i2是 x − 2 i 1 x-2^{i_1} x−2i1二进制表示的最后一位1的位置
( 0 , x − 2 i 1 − 2 i 2 − . . . − 2 i k − 1 ] (0,x-2^{i_1}-2^{i_2}-...-2^{i_{k-1}}] (0,x−2i1−2i2−...−2ik−1]包含 2 i k 2^{i_k} 2ik个数,其中 i k i_k ik是 x − 2 i 1 − 2 i 2 − . . . − 2 i k − 1 x-2^{i_1}-2^{i_2}-...-2^{i_{k-1}} x−2i1−2i2−...−2ik−1二进制表示的最后一位1的位置。

性质1:对于区间 ( L , R ] (L,R] (L,R],区间包含的数的个数是R的二进制表示的最后一位1的位置对应次幂。

这个最后一位1的位置对应次幂 可以用lowbit(x) = x & (-x)计算求得,因为计算机里的负数就是对原数的二进制取反+1

例如: 6 = 11 0 2 6=110_2 6=1102, − 6 = 01 0 2 -6=010_2 −6=0102,lowbit(6) = 110 & 010 = 10,即6最后一位1对应次幂是 1 0 2 = 2 1 10_2=2^1 102=21

再来看如何拆分区间: x = 6 = 2 2 + 2 1 x=6=2^2+2^1 x=6=22+21,二进制是 11 0 2 110_2 1102,因此 i 1 = 1 i_1=1 i1=1, i 2 = 2 i_2=2 i2=2

所以区间 ( 0 , 6 ] (0, 6] (0,6]可以拆成:
( 6 − 2 1 , 6 ] = ( 4 , 6 ] (6-2^1,6]=(4,6] (6−21,6]=(4,6],其中 i 1 = 1 i_1=1 i1=1是 6 6 6二进制表示 110 110 110最后一位1的位置
( 6 − 2 1 − 2 2 , x − 2 2 ] = ( 0 , 4 ] (6-2^1-2^2,x-2^2]=(0,4] (6−21−22,x−22]=(0,4],其中 i 2 = 2 i_2=2 i2=2是 4 4 4二进制表示 100 100 100最后一位1的位置

有了性质1,可以得到 L = R − l o b i t ( R ) L=R-lobit(R) L=R−lobit(R),故区间为 ( R − l o b i t ( R ) , R ] (R-lobit(R),R] (R−lobit(R),R]


用数组 t [ x ] t[x] t[x]表示区间 [ x − l o b i t ( x ) + 1 , x ] [x-lobit(x)+1, x] [x−lobit(x)+1,x]的和 ,则 t [ 0 ] t[0] t[0]~ t [ 8 ] t[8] t[8]如下所示
可以看到形成一个类似树的结构,例如t[8]的孩子就有 t [ 4 ] , t [ 6 ] , t [ 7 ] t[4], t[6], t[7] t[4],t[6],t[7]。


这个树有如下性质:

性质2:

通过父节点 t [ x ] t[x] t[x]找子节点:令 x ′ = x − 1 x'=x-1 x′=x−1,之后不断去掉 x ′ x' x′的最后一位1即可。

即对于 x = . . . 10...00 0 2 x=...10...000_2 x=...10...0002,得到 x ′ = x − 1 = . . . 01...11 1 2 x'=x-1=...01...111_2 x′=x−1=...01...1112,所以有孩子 . . . 01...11 1 2 ...01...111_2 ...01...1112、 . . . 01...11 0 2 ...01...110_2 ...01...1102、 . . . 01...10 0 2 ...01...100_2 ...01...1002、 . . . 01...00 0 2 ...01...000_2 ...01...0002、...、 . . . 00...00 0 2 ...00...000_2 ...00...0002

例如 t [ 8 ] t[8] t[8],得到 x ′ = 100 0 2 − 1 2 = 11 1 2 x'=1000_2-1_2=111_2 x′=10002−12=1112,则孩子有 t [ 11 1 2 ] , t [ 11 0 2 ] , t [ 10 0 2 ] t[111_2],t[110_2],t[100_2] t[1112],t[1102],t[1002]也就是 t [ 7 ] , t [ 6 ] , t [ 4 ] t[7],t[6],t[4] t[7],t[6],t[4]

通过性质2,可以由孩子求出父亲 ,例如 t [ 8 ] = a [ 8 ] + t [ 4 ] + t [ 6 ] + t [ 7 ] t[8]=a[8]+t[4]+t[6]+t[7] t[8]=a[8]+t[4]+t[6]+t[7]

查询a[1] + ... + a[x] -> for(int i = x; i > 0; i -= lobit(i)) sum += t[i]

性质3:

通过子节点找父节点:将父节点找子节点过程逆过来即可

对于孩子 x = . . . 01...10 0 2 x=...01...100_2 x=...01...1002,父亲一定是 . . . 10...00 0 2 ...10...000_2 ...10...0002,可以看到孩子只有唯一一个父亲

所以只需 . . . 01...10 0 2 + . . . 00...10 0 2 ...01...100_2+...00...100_2 ...01...1002+...00...1002即可得到父亲 . . . 10...000 ...10...000 ...10...000
也就是x + lobit(x)

通过性质3,可以找到更新原数组时所需要更新的对应的树状数组

原数组修改a[x] += c -> 树状数组修改for(int i = x; i <= n; i += lobit(i)) t[i] += c

注意:树状数组维护的位置下表只可以从1开始,如果为0则的lowbit也是0,会死循


三、例题

树状数组模板

注意

cpp 复制代码
#include <iostream>
using namespace std;

const int N = 500005;
int n, m;
int t[N], a[N];


int lowbit(int x) {
    return x & (-x);
}

int sum(int x) {
    int s = 0;
    for (int i = x; i > 0; i -= lowbit(i))   s += t[i];
    return s;
}

void add(int x, int c) {
    for (int i = x; i <= n; i += lowbit(i)) {
        t[i] += c;
    }
}

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++ ) {
        scanf("%d", &a[i]);
        add(i, a[i]);
    }
    int op, x, y;
    for (int i = 1; i <= m; i ++ ) {
        scanf("%d%d%d", &op, &x, &y);
        if (op == 1) {
            add(x, y);
        }
        else {
            printf("%d\n", sum(y) - sum(x - 1));
        }
    }
	return 0;
}
相关推荐
为什么这亚子41 分钟前
九、Go语言快速入门之map
运维·开发语言·后端·算法·云原生·golang·云计算
1 小时前
开源竞争-数据驱动成长-11/05-大专生的思考
人工智能·笔记·学习·算法·机器学习
~yY…s<#>1 小时前
【刷题17】最小栈、栈的压入弹出、逆波兰表达式
c语言·数据结构·c++·算法·leetcode
幸运超级加倍~2 小时前
软件设计师-上午题-16 算法(4-5分)
笔记·算法
yannan201903132 小时前
【算法】(Python)动态规划
python·算法·动态规划
埃菲尔铁塔_CV算法2 小时前
人工智能图像算法:开启视觉新时代的钥匙
人工智能·算法
EasyCVR2 小时前
EHOME视频平台EasyCVR视频融合平台使用OBS进行RTMP推流,WebRTC播放出现抖动、卡顿如何解决?
人工智能·算法·ffmpeg·音视频·webrtc·监控视频接入
linsa_pursuer2 小时前
快乐数算法
算法·leetcode·职场和发展
小芒果_012 小时前
P11229 [CSP-J 2024] 小木棍
c++·算法·信息学奥赛
qq_434085902 小时前
Day 52 || 739. 每日温度 、 496.下一个更大元素 I 、503.下一个更大元素II
算法