标准代码模板#
int s[N];
inline int lb(x)
{
return x&-x;
}
void add(int i,int x)
{
while(i<N)
{
s[i]+=x;
i+=lb(i);
}
}
int query(int i)
{
int ret=0;
while(i>0)
{
ret+=s[i];
i-=lb(i);
}
return ret;
}cpp可视化#
来自树状数组详解(附图解,模板及经典例题分析)- CSDN ↗

解释#
设基于 a[i] 建立树状数组 s[i],则对于一个位置 i,s[i] 将会存储 的求和。特别地,对所有奇数位置 i 有 s[i]=a[i]。
不理解?举个例子:
假设 ,那么 这几个位置都会通过若干次 i+=lb(i) 的操作转移到 ,也就是上图中的小子树。
此外,我们注意到 位置会存储全局的前缀和,因此有一个很巧妙的应用——
查找前缀和 的最小位置#
要求前缀和递增,即原数组非负。
这个本应是平衡树的任务之一,但是使用树状数组也可以实现,并且复杂度优秀、常数小。
一般做法#
二分查找位置,然后比较前缀和与 . 复杂度 .
优化#
通过倍增,与树状数组深度结合,类似 ST 表思想,思路来自 P10497 题解 ↗。
从 开始倍增 ,由于上述分析,我们知道 s[1<<p] 位置存储了全局前缀和,因此是正确的;倍增到最大的 ,使得 ,然后维护一个 sum+=s[1<<p].
接着,从新的位置 继续倍增,找到使得 的最大的 ,然后一直继续。此处,由于 保存的是 的区间和,也就是 的区间和,因此 得到的仍然是全局前缀和。继续 sum+=s[1<<p].
最终,得到的 就是最大的 的位置, 即为所求。
重新审视上述过程,发现我们其实是利用了树状数组本身的倍增机制,始终保证我们可以维护全局前缀和以供比较。
复杂度 .