二十天左右的集训结束了,虽然学校的教学完全学不到任何东西,但得承认通过自学学到了不少东西。
自学的第一个知识就是树状数组,它可以在 的时间内完成单点修改和前缀和查询。由于码量奇少,在搭配上差分数组和权值数组,就能实现许多骚操作。
树状数组的结构
以下以维护区间和为例。假设使用数组作为树状数组,它存储了原数组的某一段区间和。右端点是 ,左端点是 ,其中 是 的二进制表示中最低位的 的位置。比方说 ,那么 。
在树状数组中,每一个 都指向父亲 。在区间和的情况下, 存储了 的区间和。比如 存储了 的区间和。如下图所示:

其中每一层都等于它的子节点之和与原数组以为下标的数值之和,即。
摸清楚结构后,树状数组的单点修改和前缀和查询就非常好写了。对于单点修改,只需自底向上更新父节点即可。对于前缀和查询则反过来自顶向下累加即可。
树状数组的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| int lowbit(int x){ return x&-x; } int getsum(int x){ int ans=0; while(x>0){ ans+=c[x]; x=x-lowbit(x); }return ans; } void add(int x,int k){ while(x<=n){ c[x]+=k; x+=lowbit(x); } }
|
树状数组的其他应用
区间加
如果题目要求实现这么个操作:给定一个区间 ,使得 都加上 。这时候就可以使用差分数组和树状数组来实现。
差分数组是指原数组的相邻两个元素之差,即 。这时,可以以为原数组建立树状数组,然后对于区间 ,只需在 加上 ,在 减去 即可。这样就将区间加的操作转化为了单点加。
1 2 3 4
| void f(int l,int r,int k){ add(l,k); add(r+1,-k); }
|
接下来是查询的问题,由可得,所以只需求出的前缀和即可得到。
但这样只能求出某一个,如果要求的和,那么一个一个求时间上会很慢。甚至不如不用树状数组,这时候就需要维护第二个树状数组,原因请看公式:
我们知道
所以
可见要求出的和,只需求出$\sum{i=l}^{r}d[i]\sum{i=l}^{r}id[i]d[i]d[i]*i$的前缀和。