详细易懂 树状数组
树状数组是解决动态前缀和问题的数据结构
比如会给你N个数据,a1,a2,a3...an;
询问a1+a2+a3+...+m
修改ai(1<=i<=n)
暴力:复杂度为O(n^2)
我们来看这个树状数组图
根据图可看出 d[6]=a5+a6 6的二进制是110,所以它末尾0的个数是1,所以它需要存储2^1, 也就是两个元素
同理 d[8]=a1+a2+...+a8, 8的二进制是1000,末尾0的个数为3,所以它需要存储2^3,也就是8个元素
如果说要询问14到1的和,则直接找d[14]+d[12]+d[8]前缀后相加,它不需要将14个和相加起来,这样就可保证查询复杂度是O(logn)
那么它具体是什么原理呢,我们要询问13这个位置的前缀和,1101=13,树状数组的原理其实是个二进制拆分,
它将1101拆成三部分
1101 对应的是d[13] 2^0
1100 d[12] 2^2
1000 d[8] 2^3
每次抹掉最后一个1.因此如果我们要得到13的前缀和,则需要这三个的前缀和,十分巧妙。
所以修改数据时需要将包含这个区间的值也修改。
二进制数末尾的1在哪里,这个问题如何实现呢?
lowbit(int x){
return x&(-x)
}
它的实现是由补码表示法,感兴趣的可以去查阅资料,在这里不多赘述了。
伪代码
int d[100005]
int lowbit(int x)
{
return x&(-x);
}
//查询操作如下
int query(int x)//查询x位置的前缀和
{int res=0;
while(x)
{res+=d[x];
x-=lowbit(x);
}
return res;
}
//修改 就是查询操作倒过来
void add(int x,int v)//对d[x]加上V
{
while(x<=n)
{
d[x]+=v;
x+=lowbit(x);
}
}
树状数组还可以解决区间加/单点查询问题
区间和 ? 【5,8】=sum(8)-sum(4)
如何对区间【3,6】的每个数加上5,在3这个位置+5,7的位置上-5;意思是假如1-7的初始值都为0;3加上5,6+1=7的位置上则减5,这样查询1-2时为0,3-6时为5,而查询7这个区间为0,消除了之前+5的影响。