树状数组
前置知识-lowbit()
非负整数n在二进制表示下最低位1及其后面的的0构成的数值
eg. lowbit(44) = lowbit((101100))=(100)= 4 (4的二进制表示是100)
为了求这个数值我们可以先取反
101100 => 010011 然后再加1
010011 => 010100 这时我们发现 除了最低位的1和后面的0 其余位上的数字均与原数(101100)不同 所以让两数按位与 就能得到最后的结果
因为计算机使用的是补码 所以对一个数取反加一就是负的这个数(详见wiki)
lowbit(n)=n&(~n+1)=n&-n
正题-树状数组原理
首先我们建立下图所示的森林结构,每个节点t[x]保存以x为子树中叶节点值的和
同时我们也发现,t[x]覆盖的长度就是lowbit(x),继续观察我们还会发现t[x]的父节点就是t[x+lowbit(x)]
而且整个树的深度为log₂n+1

由此,我们能很容易得到对应操作的代码:
add(x,k)操作(t[x]+k)-单点修改
在整棵树上维护这个值,需要一层一层向上找到父节点,并按照需要依次+k
1 | void add(int x,int k) |
add(x)操作 (查询t[x]的前缀和)-区间查询
计算这个值的时候,我们只需要依次累加其左上节点的和即可
我们发现这个所谓“左上”节点正是t[x-lowbit(x)]
1 | int ask(int x) |
下面贴一个例题
add(x,y,k)操作- 区间修改
对于区间修改我们要引入一个新的概念——差分数组
假如原数组为 3 1 2 4 2 1 3
那他的差分数组就是3 -2 1 2 -2 -1 2
就是原数减去它前面的数,特别的第一个数减去0即可
差分数组有一个性质是差分数组的前缀和就是原数组
同理,前缀和数组的差分数组就是原数组
言归正传,如果要进行区间修改操作,我们不能对原数组指定范围内依次修改,因为太繁琐了,所以我们可以维护一个差分数组b,如果在(r,l)范围内进行加k操作,对于数组b,我们只需要b[r]+k,b[l+1]-k 即可。因为r到l范围内都加了k的话,除了两端的差有改变,其他差是不会改变的—— (a+c)-(b+c)=a-b
这样,我们就巧妙地完成了将区间修改转变为单点修改的转化,然后就同理add(x,k)操作喽
在此条件下还会引申出单点查询的操作
对于差分数组b我们想要得到原数组的某一个值,我们求其前缀和即可,(前面的性质提到了)同理add(x)操作
P3374 树状数组1
题目描述
如题,已知一个数列,你需要进行下面两种操作:
- 将某一个数加上 x
- 求出某区间每一个数的和
输入格式
第一行包含两个正整数 n,m,分别表示该数列数字的个数和操作的总个数。
第二行包含 n 个用空格分隔的整数,其中第 i 个数字表示数列第 i 项的初始值。
接下来 m 行每行包含 3 个整数,表示一个操作,具体如下:
1 x k含义:将第 x 个数加上 k2 x y含义:输出区间 [x,y] 内每个数的和
输出格式
输出包含若干行整数,即为所有操作 2 的结果。
输入输出样例
输入 #1复制
1 | 5 5 |
输出 #1复制
1 | 14 |
说明/提示
【数据范围】
对于 30% 的数据,1≤n≤8,1≤m≤10;
对于 70% 的数据,1≤n,m≤104;
对于 100% 的数据,1≤n,m≤5×105。
数据保证对于任意时刻,a 的任意子区间(包括长度为 1 和 n 的子区间)和均在 [−231**,231**) 范围内。
1 |
|
P3368 树状数组2
题目描述
如题,已知一个数列,你需要进行下面两种操作:
- 将某区间每一个数加上 x;
- 求出某一个数的值。
输入格式
第一行包含两个整数 N、M,分别表示该数列数字的个数和操作的总个数。
第二行包含 N 个用空格分隔的整数,其中第 i 个数字表示数列第 i 项的初始值。
接下来 M 行每行包含 2 或 4个整数,表示一个操作,具体如下:
操作 1: 格式:1 x y k 含义:将区间 [x,y] 内每个数加上 k;
操作 2: 格式:2 x 含义:输出第 x 个数的值。
输出格式
输出包含若干行整数,即为所有操作 2 的结果。
输入输出样例
输入 #1复制
1 | 5 5 |
输出 #1复制
1 | 6 |
1 |
|