树状数组用于在log(n)的时间复杂度修改与询问前缀
相比线段树更好写 常数更小 不过局限性很大 不能用于维护最大最小值之类的情况
最常用的应用(我用过的)大概有:
单点修改区间查询
区间修改单点查询
区间修改区间查询
离散化权值求逆序对
let’s begin
以上内容转自洛谷金秋讲义
上面已经把树状数组定义以及修改查询方法讲的很细致了
值得特别提出来的是lowbit的证明部分:
设A’为A的二进制反码,i的二进制表示成xx1yy,其中yy为全0序列,xx可以忽略(因为对lowbit没有贡献最后都会归零) 那么-i = x’x’0 y’y’+1(计算机中负数的存储方式 反码+1) 由于yy是全0 序列 那么反码y’y’就是全1序列 再加上1 那就又变成了全0序列 首位进1 那么-i=x’x’1yy 所以呢 i&-i=xx1yy&x’x’1yy=1yy 即2^k的值(k是i二进制末尾0的个数)
接下来是基本代码:
int lowbit(int x){
return x&(-x);
}
void add(ll c[],int x,int k)
{
while(x<=n)
{
c[x]+=k;
x+=lowbit(x);
}
}
ll query(ll c[],int x)
{
ll ans=0;
while(x)
{
ans+=c[x];
x-=lowbit(x);
}
return ans;
}
有了上面的代码就可以:
单点修改区间查询:
反正能够log(n)修改,
log(n)查询前缀和,
减一减就是了
关于逆序对:
离散化之后倒序加入每次统计前缀和(后边比当前小的数的个数)
区间修改单点查询:
这个需要用树状数组维护一个记录每个点修改情况的差分数组delta,在修改区间(l,r)的时候只需要修改r+1 和 l
查询x时输出a[x]+sum[x]就可以
区间修改区间查询:
这个比较麻烦了
需要推一推式子
上面区间修改时利用了差分数组delta
即 a’[ x ] = a[ x ]+delta[ 1 ]+delta[ 2 ] … + delta[ x ]
区间查询时用到了前缀和数组
那么我们可不可以综合起来用来实现区间修改区间查询呢
当然了!
来看下面
a’[ x ] = a[ x ]+delta[ 1 ]+delta[ 2 ] … + delta[ x ]
a’[ x-1 ] = a[ x-1 ]+delta[ 1 ]+ … + delta[ x-1 ]
······
a’[ 1 ] = a[ 1 ]+delta[ 1 ]
==>sum’[ x ] = sum [ x ] + x*delta[ 1 ] +(x-1) * delta[ 2 ]+..+1 * delta[ x ]
观察右边delta类似一个倒三角
大概是 。。这样
这样不好弄啊
那我们把他补一下 像这样
那么我们发现
只需要维护一个delta[ ]的前缀和 一个delta[x] * x 的前缀和就可以求出后边一部分式子了
即 红色部分 = delta[ ]前缀和(x+1) (矩形)- delta[x] x 前缀和(蓝色部分)
于是每次查询(l , r)就变成了
ans=( sum[r]+ deltasum[ r ] * (r+1) - deltaxsum[ r ])-( sum[ l-1 ] +deltasum[ l-1 ]* l -deltaxsum[ l-1 ] )
THE END