看图理解树状数组还是很好理解树状数组的:
1.树状数组是干嘛的:
解决动态前缀和问题的数据结构 a1+a2+a3....an
2.询问: a1+a2+a3...am(m是变化的)
3.修改 ai(1<=i<=n)
谈谈询问:
设结点编号为x,那么这个结点管辖的区间为2^k(其中k为x二进制末尾0的个数)个元素,
d[6]=a5+a6; 110 2^1; //d是树状数组,a数组是我们题目或者要用的数组,我们可以把它改成树状数组d
d[8]=a1+...a8; 1000 2^3;
比如d[6]是a5+a6 a:那么我们如何知道d[6]是a5+a6? 6的二进制是110 注意红字 那么就是2^1=2也就是它管辖的区间是俩个,看上面的图来理解, b:那么如何知道是a5+a6这俩个?
比如我们要询问13这个位置的前缀和
叙述询问过程:
1101 = 13 //二进制拆分
拆分:每次去掉最后一个一
1101 d[13]; 2^0
1100 d[12]; 2^2
1000 d[8]; 2^3
从上图也可以看出13位置的前缀和是d[13]+d[12]+d[8];
c:那么问题由来了,我们该如何实现这个过程?我们先看一个代码
int lowbit(int x)
{
return x&(-x);
}
这个代码是啥意思?首先我们要知道一个正整数的负数的补码是这个正整数的原码取反加一
比如13的二进制
13的原码 1101
补码 0010+1 = 0011
二进制&: 1101
&0011
0001
然后用原码减去&的结果:1101
-0001
1100
可以看出和上次的叙述过程是一样的那么就循环做这个事不就行了,一直到0结束那么就有了查询的代码实现了:
int query()//查询x位置的前缀和
{
int res=0;
while(x){
res+=d[x];
x-=lowbit(x);
}
return res;
}
虽然写出来,但是我们可以看看这个过程的巧妙性:
还是以13举例子:当我们&出来的结果我们可以发现它是末尾少个一的,不就和叙述过程一样么,这点是很巧妙的
谈谈修改:
如果我们想修改某一树状数组的结点,那么动一点,包含它的点也得动才行啊,那么这个问题怎么解决?
其实询问是按照每个结点往下询问,一直到结束,那么修改不就是往上么,一直上到包含这个点的结点结束不就行了,那么代码就可以实现出来了:
void add(int x)//修改操作 其实就把查询操作到过来
{
while(x<=n){
d[x]+=v;
x+=lowbit(x);
}
}
树状数组单次查询复杂度0(logn);
然后可以看一题例题:树状数组简单例题