主要解决的问题
对于n个数,有修改和查询操作
单点修改区间查询(对于第i个数增加或减少一个值,然后求一个区间的值,这个区间也可以是一个点)
区间修改单点查询 (对于一个区间都增加或减少一个值,然后求一个点的值,这类题我们让每个点先记录的是和前面值的差,那么一个点的前缀和就是这个点的值,对于修改一个区间,只需要这个区间最左面的点加上这个值,最后面+1那个点减去这个值,这样对于每个点的前缀和,只有这个区间里的点有改变,所以整体操作还是只修改了一个点)
代码量比线段树少很多,但是解决的问题也少很多。
其中0号点不能存值
a[i]存每个点的值,f[i]存树状数组每个点的值
Lowbit(i)=x&(-x) 是计算一个数最低位1所代表的值,例如lowbit(6)=2因为110中10=2
操作有两个
Add(i,v) i号点增加v
Su(i) 前i个点的值求和
在树状数组中数组的每个点记录的并不是某个点的值,它记录的是某个点二进制下,去掉最低位1然后加1,一直到本个数之间的值,例如12号点,他的二进制是1100,他其实记录的是1001,1010,1011,1100的值的和
对于i点进行add操作,最初和更新后的每一个i=i+lowbit(i)&&i<=n的f[i]都要增加值v
所以反过来看的话 对a数组中1010增加v的话,f数组中1010,1100,10000.....都要增加v(省略号有多少,就要看整体的数到底有多少,n的大小)
因为对于n最多有lgn位,操作最多就是lgn次
而su就是求前缀和,例如1100只能求出1000+1~1100的数,所以还差0+1~1000,而些数恰好在1000中,容易看出,只要求出每一个i=i-lowbit(i)&&i!=0的f[i]值之和即可
因此也跟1的个数有关,所以操作也是lgn次
int lowbit(int x)
{
return x&(-x);
}
void add(int x,int v)
{
while(x<=n)
{
f[x]+=v;
x+=lowbit(x);
}
}
int su(int x)
{
int sum=0;
while(x)
{
sum+=f[x];
x-=lowbit(x);
}
return sum;
}
树状数组还可以进行二维的修改的查询,例如对于一个矩阵要修改一个区间,然后查询一个点,就可以同样用前缀的思想,每个点到源点形成的小矩阵记录这个点a[i][j]的值
那么修改一个矩阵(x,y)(xx,yy),若0<x<=xx,0<y<=yy, 只需要用add函数把(x,y),(xx+1,yy+1)增加v,(x,yy+1),(xx+1,yy)减去v即可(1维数组改两个点,二维改4个...)
对于n*m的矩阵
void add(int x,int y,int v)
{
int yy=y;
while(x<=n)
{
y=yy;
while(y<=m)
{
f[x][y]+=v;
y+=lowbit(y);
}
x+=lowbit(x);
}
}