20.树状数组
树状数组
树状数组用于维护和查询区间前缀和
二进制编码
树状数组时利用数的二进制特征进行检索的一种树状的结构

我们发现,圈里带数字的节点我们定义为
这样子我们就能把前缀和的查询控制在
考虑维护,我们发现当有一个
观察发现,查询操作实际上是每次去掉二进制下的最后一个
维护操作是每次在二进制的最后的
例如如果修改了
所以,树状数组的关键就是如何找到一个数的二进制的最后一个
我们利用负数的补码表示,可以得到二进制下的最后一个
例如

惊奇的发现,

这样子利用了数的二进制下的一些特性就可以把查询优化到
算法实现
单点修改+区间查询
我们很容易就能写出来
struct tree{
int n;
vector<int> c;
tree(int n){this->n=n;c.assign(n+1,0);}
void add(int x,int val){
for(;x<=n;x+=x&-x)c[x]+=val;
}
int sum(int x){
int ret=0;
for(;x;x-=x&-x)ret+=c[x];
return ret;
}
}
区间修改+单点查询
我们利用前缀和的思想,如果想要在
区间修改+区间查询
这里只用一个树状数组是做不到了,我们需要差分数组+树状数组的深度结合
求区间和
定义
就化简成了两个和式的形式,就可以用树状数组来处理了,一个实现
tree t1(n),t2(n);
vector<int> a(n+1,0);
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++){
int d=a[i]-a[i-1];
t1.add_x(i,d);
t2.add_x(i,(i-1)*d);
}
while(m--){
int op;cin>>op;
if(op==1){
int L,R,d;cin>>L>>R>>d;
t1.add_x(L,d);t1.add_x(R+1,-d);
t2.add_x(L,(L-1)*d);t2.add_x(R+1,-(R+1-1)*d);
}
else{
int L,R;cin>>L>>R;
cout<<R*t1.get(R)-t2.get(R)-((L-1)*t1.get(L-1)-t2.get(L-1))<<'\n';
}
}
二维区间修改+区间查询
二维其实是一维的拓展,也是考虑构造差分数组
然后需要查询
所以问题就转化为如何求
所以,我们只需要构造四个树状数组来维护
查询和求和的复杂度都为
区间最值
其实树状数组也可以解决区间最值问题

回到这个图,前缀和树状数组中,
单点修改:
如何修改,对于祖先节点,重新求一次
void update(int x,int val){
while(x<=n){
c[x]=val;
for(int i=1;i<lowbit(x);i<<=1)
c[x]=max(c[x],c[x-i]);
x+=lowbit(x);
}
}
区间最值查询:
-
如果
那么 区间包含了 所包含的所有结点,还多出来一块 则 -
如果
那么 区间在 所包含的区间内,则往前递推
可以写成递推的形式
int query(int L,int R){
int ans=0;
while(L<=R){
ans=max(ans,a[R]);R--;
while(R-L>=lowbit(R)){
ans=max(ans,c[R]);
R-=lowbit(R);
}
}
return ans;
}
更新和查询的时间复杂度都为