树状数组的思想原理:前缀和加上类似倍增的思想,相当于前缀和的进阶版
树状数组的序号必须从1开始。
实现原理
每个元素用二进制表示,最右侧的‘1’是储存的区间和的区间长度
lowbit:i&(-i)就是求i的最右边的‘1’
每个元素x储存的区间:[x-lowbit[x]+1,x](理解为:【当前位置-区间长度+1,当前位置】。比如12,储存的就是[9,12]的区间和)
基本应用
正常树状数组:范围查询,单点修改
差分树状数组:范围修改,单点查询
经典应用与原理
前缀和查询(范围[1,i],返回ans):
(i对应的树状数组元素所储存的区间是[i-lowbit[i]+1,i])
1.ans加上i对应的树状数组元素(就是[i-lowbit[i]+1,i]的区间和)
2.i减去lowbit(i)。由于已经算出[i-lowbit[i]+1,i]的区间和,所以将区间缩小到[0,i-lowbit(i)]。如果i==0,说明已经算完了。
代码
int sum(int i){
int ans = 0;
while(i>0){
ans+=tree[i];
i-=lowbit(i);
}
return ans;
}
区间和查询(范围[l,r])
代码
int query(int l,int r){
return sum(r)-sum(l-1);
}
单点修改(位置i,加上k)
目标就是找到哪些树状数组元素包含这个位置i。
而i+lowbit(i)一定包含i。
代码
void add(int i,int k){
while(i<=n){
tree[i]+=k;
i+=lowbit(i);
}
}
树状数组实现区间最值查询
修改
用一个单独的数组储存当前数组的元素。
对于x,可以转移到x的只有,x-2^0,…….,x-2^k (k满足2^k < lowbit(x)且2^(k+1)>=lowbit(x))
所以tr[x] = max(tr[x],tr[x-k]);(k满足2^k < lowbit(x)且2^(k+1)>=lowbit(x))
代码
void update(int x)
{
int lx, k;
while (x <= n)
{
tr[x] = a[x]; //该元素已经在a中修改了
lx = lowbit(x);
for (k=1; k<lx; k<<=1)
tr[x] = max(tr[x], tr[x-k]);
x += lowbit(x);
}
}
区间查询
因为tr[y]表示的是[y,y-lowbit(y)+1]的最大值。
所以,可以这样求解:
若y-lowbit(y) > x ,则query(x,y) = max( tr[y] , query(x, y-lowbit(y)) );
也就是包含在x–y这个区间中
若y-lowbit(y) <=x,则query(x,y) = max( a[y] , query(x, y-1));
这种情况就是超出了x–y的范围,所以不能直接用y–lowbit(y),只能减少y。
时间复杂度是O((logn)^2)
代码
int query(int x, int y)
{
int ans = 0;
while (y >= x)
{
ans = max(a[y], ans);
for (--y; y-lowbit(y) >= x; y -= lowbit(y))
ans = max(tr[y], ans);
}
return ans;
}