(家人这是我在其他地方借鉴的,真的真的感觉很好就记录分享给大家啦~,也顺便当成个笔记方便回顾(逃~))
树状数组是用数据压缩的思想由二进制实现的数据结构。有单点修改+区间查询或区间修改+单点查询的作用。
实现单点修改&区间查询
首先我们来看看暴力的效率。q组询问,极端情况下n个数的修改,效率为 O(n q) 。n,q为500000时一定会炸。
切入点:lowbit函数
由于电脑一种叫做补码的操作(由于电脑是二进制,它们存的相反数是它的取反+1),一个数与它的相反数做与操作时会返回二进制下最右边的1的位置。举个例子: 6&-6=2 将6变成二进制:110。其中最右侧的1加粗字体:110则返回的是二进制下10的值:2。得到代码:
//ll就是long long
ll lowbit(ll num){
return num&-num;
}
利用这个性质建立树状数组:
为了简化区间修改的效率,我们需要建立这样的一个数组:数组中第k位的值为原数组中的一段区间和,这个区间的长度是lowbit(k),终点是k。 比如:输入一个数组,那么我们所建立的数组:
- 第一位(1在二进制下=1 二进制下的1=1)的值为输入的数组的第一位往前的一位的和,也就是第一位。
- 第二位(2在二进制下=10 二进制下的10=2)的值为输入的数组的第二位往前两位的和,第一位和第二位。
- 第三位(3在二进制下=11 二进制下的1=1)的值为输入的数组的第三位往前一位的和,也就是第三位。
- 第四位的值(4在二进制下=100 二进制下的100=4)的值为输入的数组的第四位往前四位的和,也就是第一位,第二位,第三位以及第四位。
我们就称这种数组为树状数组
代码
void build(ll s,ll num){
for(ll i=s;i<=n;i+=lowbit(i)) tree[i]+=num;//当s在i的范围内 第num位数组加上num
}
跟着代码走一遍:
- 假设输入的n=5,输入的数为1 5 4 2 3(样例)
- 输入第一个数时s=1,num=1。加上的树状数组数组位数为第一位,第二位,第四位。树状数组为:1 1 0 1 0
- 输入第二个数时s=2,num=5,加上的位置为第二位,第四位。数组为1 6 0 6 0
- 输入第三个数时s=3,num=4,加上的位置为第三位。数组为1 6 4 10 0
- 输入第四个数时s=4,num=2,加上的位置为第四位。数组为1 6 4 12 0
- 输入第五个数时s=5,num=3,加上的位置为第五位。数组为1 6 4 12 3
至此,建树操作已经完成了。可以发现这个操作的本质就是修改树状数组的值,所以它也是单点修改的函数。
查询也是一样的。注意我们的操作不是区间求和,而是求两个前缀和的差!
//反着的建树。此操作是求输入的数据第1到第s位的和。
ll ask(ll s){
ll ans=0;
for(ll i=s;i>=1;i-=lowbit(i)) ans+=tree[i];//建树的反操作
return ans;
}
举一个sample(example) 求第三项和第五项之间的和。 本质上就是求5的前缀和与2的前缀和的差。(1 2 3 4 5)-(1 2)=(3 4 5)
- 先求5的前缀和。所求的就是第5(101)项+第4(100)项就是12+3=15。
- 再求2的前缀和。所求的就是第2(10)项就是6。
- 最后作差就是15-6=9。
检验一下,3到5位的求和就是4+2+3=9。没有问题!
把所有的结合起来就是树状数组啦~
代码
ACcode:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=5e5+10;
int n,m,q,in[N],tr[N];
int lowbit(int num) {
return num&-num;//返回值为二进制下num从左往右第一个1的位置
}
void add(int s,int num) {
for(int i=s; i<=n; i+=lowbit(i))tr[i]+=num;
//当s在i的范围内,第num位数组加上num
}
int ask(int s) {
int ans=0;
for(int i=s; i>=1; i-=lowbit(i))ans+=tr[i];//寻找差分的标记
return ans;
}
void solve() {
cin>>n>>q;
for(int i=1; i<=n; i++) cin>>in[i];
while(q--) {
int op;
cin>>op;
if(op==1) {
int x,y,s;
cin>>x>>y>>s;
add(x,s);//差分
add(y+1,-s);
} else {
int x;
cin>>x;
cout<<in[x]+ask(x)<<"\n";//区域查询则为右边界前缀和减去左边界前缀和
}
}
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int t=1;
// cin>>t;
while(t--) {
solve();
}
return 0;
}
over~