基础和原理可以看看我写的总结:http://blog.csdn.net/WilliamSun0122/article/details/70679766
这个用法是树状数组最基础的用法,我的总结里面就是以这个用法为例讲的。直接贴模板,详细见我树状数组总结的博客。
int lowbit(int x)
{
return x&(-x);
}
int update(int x,int value)
{
while(x<=maxn)
{
sz[x] += value;
x += lowbit(x);
}
}
int query(int x)
{
int ans = 0;
while(x>0)
{
ans += sz[x];
x -= lowbit(x);
}
return ans;
}
该种用法详情见我以hdu1556为模板写的博客:http://blog.csdn.net/WilliamSun0122/article/details/71511879
简单说一下:
这种用法主要是通过维护差分数组实现的。
设原数组是a[n],差分数组c[n],c[i]=a[i]-a[i-1],那么明显地a[n]= ∑ni=1c[i] ,如果想要修改a[i]到a[j] (比如+v),只需令c[i]+=v,c[j+1]-=v即可,到这区间更新就实现了。
单点查询就直接查询查询即可。因为你维护的是差分数组c[],查询时query(n)是查询c[1]+…+c[n],而其就等于 a[n],即实现单点查询。
比如对a[]的区间[3,5]都+1
核心代码:
//lowbit(),update(),query()与一维单点更新,区间查询一样
update(3,1);
update(6.-1);
会了之后可以去把hdu1556模板题秒了。
该种用法详情见我以poj3468为模板写的博客:http://blog.csdn.net/williamsun0122/article/details/71571143
简单说一下:
这种用法是维护差分数组c[n]的同时维护另一数组c2[n] = n*c[n]。区间更新与一维 区间更新 单点查询一样。区间查询:
a[1] + a[2] + … + a[n]
= c[1] + (c[1]+c[2]) + … + (c[1]+c[2]+…+c[n])
= n*c[1] + (n-1)*c[2] + … + c[n]
= (n+1)*(c[1]+…+c[n]) - (1*c[1] + 2*c[2] + … + n*c[n])
模板:
//lowbit()与之前一样
void update(int x,int value)
{
int i=x;
while(x<=n)
{
c1[x] += value;
c2[x] += i*value;
x += lowbit(x);
}
}
int query(int x)
{
int tmp1=0,tmp2=0,i=x;
while(x>0)
{
tmp1 += c1[x];
tmp2 += c2[x];
x -= lowbit(x);
}
return (i+1)*tmp1-tmp2;
}
多维与一维没有本质差别,可以通过一维类推得到。以二维为例
详细见我以hdu2642为模板写的博客:http://blog.csdn.net/WilliamSun0122/article/details/71642358
想知道二维树状数组具体构成就看看上面的博客。
其实没什么说的,直接一维类推即可。查询就是查询一个矩形区域。
// lowbit()是一样的
void update(int x,int y,int value)
{
for(int i=x;i<=maxn;i+=lowbit(i))
for(int j=y;j<=maxn;j+=lowbit(j))
sz[i][j] += value;
}
int query(int x,int y)
{
int ans = 0;
for(int i=x;i>0;i-=lowbit(i))
for(int j=y;j>0;j-=lowbit(j))
ans += sz[i][j];
return ans;
}
//比如我们要查询x1,y1,x2,y2这个矩形区间的和(x1
//query(x2,y2)-query(x1-1,y2)-query(x2,y1-1)+query(x1-1,y1-1)
详细见我以poj2155为模板写的博客:http://blog.csdn.net/WilliamSun0122/article/details/71763373
多维以上有关区间都用了容斥原理。
就像这个图,我们想对蓝色区间内修改一次,那么如果我们改变第一个蓝色的点会造成紫色区间内的修改,所以我们必须再通过修改黄、绿、红来抵消这些多余的影响。
//区间更新(x1,y1),(x2,y2)
//(x1<=x2,y1<=y2)(与建立坐标系有关,我建立在第一象限)
update(x1,y1);
update(x1,y2+1);
update(x2+1,y1);
update(x2+1,y2+1);
//单点查询直接查询即可
我没有找到模板题目,只能在这里详细说说了。(感兴趣可以自己找线段树的题用树状数组写)
我找到一个很不错的博客:(这个可能看似和我之前讲的一维区间更新、区间查询不太一样,但仔细思考一下会发现是一样的)http://www.cnblogs.com/hefenghhhh/p/6554594.html
我们知道一维区间更新是由一个差分数组实现的,对[s,t]区间增加x,利用差分的思想,只需要让delta[s]+x,delta[t+1]−x即可。其中delta[i]表示从i到n的区间的增量。
其中num'表示修改过后的数组,num表示原数组,delta[i]表示从i到n区间的增量。查询区间sum(s,t)=∑ti=snum′[i]+∑ti=s(delta[i]∗(t−i+1))。
其中num'表示修改过后的数组,num表示原数组,delta[i]表示从i到n区间的增量。而∑ti=s(delta[i]∗(t−i+1))=(t+1)∑ti=sdelta[i]−∑i=st(delta[i]∗i)。
我们用两个树状数组,一个维护delta[i],一个维护delta[i]∗i,那么上面的区间修改和区间查询均可以在O(logN)的时间内完成了。
二维区间更新上面已经讲了,我们来看看区间查询。
设(x1,y1)和(x2,y2),(x1<=x2,y1<=y2)
sum(x1,y1,x2,y2)=sum(1,1,x2,y2)−sum(1,1,x2,y1−1)−sum(1,1,x1−1,y2)+sum(1,1,x1−1,y1−1)
那么我们只需知道如何查询类似区间sum(1,1,x1,y1)就可以了。
sum(1,1,x1,y1)=∑x1i=1∑y1j=1num[i][j]+∑x1i=1∑y1j=1delta[i][j]∗(x1−i+1)∗(y1−j+1)
而
∑x1i=1∑y1j=1delta[i][j]∗(x1−i+1)∗(y1−j+1)=(x1+1)∗(y1+1)∗∑x1i=1∑y1j=1delta[i][j]−(x1+1)∗∑x1i=1∑y1j=1j∗delta[i][j]−(y1+1)∗∑x1i=1∑y1j=1i∗delta[i][j]+∑x1i=1∑y1j=1i∗j∗delta[i][j]
所以我们维护四个树状数组即可delta[i][j],delta[i][j]∗i,delta[i][j]∗j,delta[i][j]∗i∗j。
类似一维区间更新、区间查询里面维护两个数组。