引入
什么是树状数组???
树状数组就是用数组来模拟树形结构 ,方便完成之后的操作
树状数组的作用是什么?有什么优势?
树状数组是为区间操作而生的 ,能够有效的解决单点更新 ,区间查询的操作可以将求和的时间复杂度从 简化为
话不多说 ,先看例题
BIT-1
题目描述
共有 n 个数,进行 q 次操作 ,操作有两种:
1 i x : 将 a[i] 加上 x;
2 l r : 求 a[l] + ... + a[r];
第一行:输入两个数 n,q,表示给定数组的长度和操作数
第二行:输入n个数,表示长度为n的给定数组
接下来q行:每行输入 3 个数字,表示如题的某一种操作
对于每个 2 操作 ,输出对应结果
解题思路
如果采用最朴素的普通数组去做 ,修改为 ,查询为
如果使用前缀和 ,修改为 ,查询为
既然这两种方法都会超时 ,我们就要用到树状数组
树状数组
· 构造
我们用 a 数组来存储原数组 ,用 c 数组来模拟树的结构 ,把 a 数组看成是这个树形结构的叶子 ,就得到了下面这张图:
每个节点中存储的元素就是它的子树的所有元素之和 ,
那数据的存储有什么规律呢?
根据这两张图片,我们可以发现,c[i] 的层数取决于 i 的二进制的末尾的 0 的个数 ,要找出共有多少个 0 ,就等于寻找最后一个 1 的位置 ,想要实现这个过程十分简单 ,只需要一个位运算
long long lowbit(long long x){
return x & (-x);
}
· 单点修改
从图中 ,我们可以发现 i+lowbit(i) 所得的值是 i 的父节点 ,所以我们修改 a[i] ,c数组中 i 的所有祖先也都需要修改 ,顺着 i 一直往上找到父节点进行修改即可
void update(int x, int val){
for(;x <= n;x += lowbit(x))
c[x] += val;
}
· 查询
举例 :查询 的值 ,sum = c[7] + c[6] + c[4]
7 6 4 二进制分别为 111 110 100 ,每次减去 lowbit(i) 求和即可得出结果
long long query(long long x){
long long sum = 0;
for(;x;x -= lowbit(x))
ans += c[x];
return ans;
}
模板代码
#include<bits/stdc++.h>
using namespace std;
int n,q;
long long c[1000005];
int lowbit(int t){
return t&(-t);
}
void add(int x,int k){
for(int i=x;i<=n;i+=lowbit(i)){
c[i]+=k;
}
}
long long sum(int x){
long long ans=0;
for(int i=x;i;i-=lowbit(i)) ans+=c[i];
return ans;
}
int main(){
int x;
scanf("%d%d",&n,&q);
for(int i=1;i<=n;i++){
scanf("%d",&x); add(i,x);
}
while(q--){
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
if(x==1) add(y,z);
else printf("%lld\n",sum(z)-sum(y-1));
}
return 0;
}
BIT-2
题目大意
共有 n 个数,进行 q 次操作 ,操作有两种:
1 l r k 将a[l]~a[r]每个数 + k
2 k 输出a[k]
对于每个操作 2 ,输出对应结果
解题思路
采用差分的思想 ,因为差分是前缀和的逆运算 ,所以我们用 c 来表示维护一个差分数组 ,每次update把 c[l] ~ c[k] 加 k ,每次query时计算c[1]~c[k]的前缀和即可