树状数组
BY RBCORN
1.前缀和
为了方便区间求和,前缀和应运而生。
具体来说,前缀和是一种预处理操作。在得到数据后,对于数组里的每个位置 i , 前缀和pre[i]代表数组从头到当前位置的求和,即
计算前缀和时,为从1到i直接累加
而前缀和的查询为O(1),但是修改则需要O(n)。
此时我们需要一个更快速的方法——树状数组
2.树状数组
具体思想
树状数组同样是一种预处理操作
树状数组,顾名思义,是一个数组对应着一棵树,大概如此:
显而易见,数组中每个位置对应的树的结点中,只有最上层存放数据,而它的更新和查询,都是靠着上面这棵树。在这颗树中,树中位置i所对应的亮点记作tree[i]。暗点的值不做记录。
先是更新。将位置i的值增加一定的量x,则要在树状数组里一直向上,把途径的每个亮点都加上x。
容易知道每个tree[i]中存储的是以树中结点i为根的子树的叶子节点a[j]之和,
根据上面这句话,我们可以查询前缀和:
在这个例子中,pre[3]=tree[2]+tree[3]。
现在我们可以知道,更新和查询这两种操作,时间复杂度都为O(log n)。
对比前缀和,显然在大部分情况下此方法更快。
代码实现
lowbit:
lowbit(x)是x的进制表达式中数位最低的1所对应的值。例如 lowbit((1000101100)_2)=(100)_2
lowbit(x)=x&(-x)。一个数的相反数就是其取反加一所得到的值,若末尾有一些0,则取反之后都会变成1,再加一之后就会从右往左不断进位,直到遇上第一个0(原数中的1),进位停止,到此时,从数位最低的1开始,后面的数(包括1)都与原数相同,而1之前与原数不同,实现了lowbit操作。
更新操作:
现在需要解决的问题是:如何找到一个点的父结点?这里就需要用到lowbit。在树状数组中,寻找父节点,就是逐渐向高位,向上取整。可以看几个例子:
0001->0010->0100->1000
0011->0100->1000
而向更高一位且向上取整,就是让一个数加上自己的lowbit。因为在二进制中1+1=10正好进位,所以加上去的lowbit与原数中的lowbit相结合,便产生了进位,能够向更高位并向上取整。而更高位可能又产生进位(例如11可能本身想要取整到第二位,但是第二位又产生了进位,所以是100)。
(这一步理解起来比较困难,可以自己试着举几个例子)
更新时,找到了父节点,更新它后,需要找父节点的父节点,继续更新,直到根节点(根节点也需要更新)
void update(int pos,int val){//更新操作 pos=需要更新的位置 val=在当前位置加上的值
while(pos<=n){
tree[pos]+=val;//更新当前结点
pos+=lowbit(pos);//寻找父节点
}
}
查询操作:
查询前缀和。
查询中,我们需要找到几个树中的结点,能够涵盖从1到当前位置i中所有值的和。
思路与插入类似,不过这次是不断向高位并向下取整。这样做,类似于找一个二进制数中有哪几位为1。找到一个1后,就可以把它去掉了,也就是减去目前的lowbit,然后累加树结点中的值,得到答案。看上面的动画就能够理解这点。
代码如下:
int query(int pos){//pos的前缀和
int ans=0;//记录答案
while(pos){//相当于pos!=0
ans+=tree[pos];//累加答案
pos-=lob(pos);//寻找下一节点
}
return ans;
}
完整程序:
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
#define rep(i,e) for(int i=1;i<=e;i++)
#define mmm(a,x) memset(a,x,sizeof(a))
#define rep_e(i,p,u) for(int i=head[p],u=to[i];i;i=nxt[i],u=to[i])
#define lob(i) (i&(-i))
typedef long long LL;
const int maxn=5e5+9;
LL n;
LL a[maxn],tr[maxn];//tr=tree
void update(int pos,LL val){
while(pos<=n){
tr[pos]+=val;
pos+=lob(pos);
}
}
LL query(int pos){
LL ans=0;
while(pos){
ans+=tr[pos];
pos-=lob(pos);
}
return ans;
}
int main(){
cin>>n;//数组大小
rep(i,n){
cin>>a[i];
update(i,a[i]);
}
int op; cin>>op;//op=1更新 op=2查询
if(op==1){
int pos; LL val;
cin>>pos>>val;
update(pos,val);
}
else{
int r,l;
cin>>l>>r;
cout<<query(r)-query(l-1)<<endl;
}
return 0;
}