网上有很多关于树状数组的博客,有很多也讲得很好。
但我感觉对于树状数组,自己始终只能去套模板,所以打算自己写一篇博客,梳理梳理树状数组的用法。
首先上一篇博客。树状数组入门
感觉很无敌,入门足够用了。
我们用C[] 数组来储存树状数组中的信息。
1=(001) C[1]=A[1];
2=(010) C[2]=A[1]+A[2];
3=(011) C[3]=A[3];
4=(100) C[4]=A[1]+A[2]+A[3]+A[4];
5=(101) C[5]=A[5];
6=(110) C[6]=A[5]+A[6];
7=(111) C[7]=A[7];
8=(1000) C[8]=A[1]+A[2]+A[3]+A[4]+A[5]+A[6]+A[7]+A[8];
找规律发现,C[i]所代表的元素是原数组中以i结尾的,连续的,2^k 个元素。(这里的k其实可以代表这个元素在树中的高度)
所以可以轻松发现这其中有前缀和的性质。
例如前3(11)个元素 就是C[3(11)]+C[2(10)]
再例如前6(110)个元素 就是C[110]+C[100]
每次去除一个末尾1 直至下标为0。
那么 利用前缀和的思想,我们就可以很轻松的求出任意连续区间内的数字的和。
再然后就是修改操作。 每修改一个元素,我们都要修改与之相关的所有元素。
由于树的最高高度为logn层,每层只修改一个元素,所以最多修改logn个元素,因此修改操作的复杂度为O(logn)而普通的前缀和修改一个元素需要O(n)的时间。
同理,对于查询操作,其复杂度也为O(logn).
树状数组模板如下
int lowbit(int x){return x&(-x);}
void add(int i,int val)
{
for(;i<=n;i+=lowbit(i))
bit[i]+=val;
}
int query(int i)
{
int ans=0;
for(;i>=1;i-=lowbit(i))
ans+=bit[i];
return ans;
}
然后树状数组很容易推广到二维,相当于n个树状树状数组加起来。
const int MAX=2555;
int seed=6666;
int n,m,q;
int lowbit(int x)
{
return x&(-x);
}
int c[MAX][MAX];
int sum(int i,int j)
{
int tempj,sums=0;
while(i>0)
{
tempj=j;
while(tempj>0)
{
sums+=c[i][tempj];
tempj-=lowbit(tempj);
}
i-=lowbit(i);
}
return sums;
}
void update(int x,int y,long long num)
{
for(int i=x;i<=n;i+=lowbit(i))
{
for(int j=y;j<=m;j+=lowbit(j))
{
c[i][j]+=num;
}
}
}
未完待续。