树状数组
(网上的许多博客讲解树状数组的时候都把他当做“一棵树”来讲解,但我认为把他当成“数组”来理解反而更加清晰易懂)
先引入一下 \(lowbit\): 设有一个数 \(x\), 那么 \(lowbit(x)\) 就是把 \(x\) 用二进制表示后,从右到左第一个 \(1\) 所表示的权值。例如,\(lowbit(5)=lowbit(101)=1, lowbit(10)=lowbit(1010)=2, lowbit(8)=8\). (\(lowbit\) 也表示最大能整除 \(x\) 的 \(2\) 的幂)
树状数组具体实现:我们设原数组为 \(a\), 树状数组为 \(c\). 其中,\(c_i\) 表示 \(a[i]+a[i-1]+...+a[i-lowbit(i)+1]\), 即从 \(a_i\) 开始(包括 \(a_i\))的前面的 \(lowbit(i)\) 个数的和。
查询
—— 如何查询 \(a_1+a_2+...+a_x\) 呢?
while(x){
//x 代表着“现在还有 a_1, a_2...a_x 这些数没有加到 ans 里”
ans += c[x];//运行完这一行表示 a_x 前 lowbit(x) 个数(包括 a_x)已经加到 ans 里了,
x -= lowbit(x);//根据上一行的解释和 x 的定义,x -= lowbit(x) 是显然的
}
至于如何查询 \([l, r]\) 的和,只需用 \(query(r)-query(l-1)\) 即可。
单点修改
—— 要给 \(a_x\) 加上 \(k\),\(c\) 数组需要怎么修改呢?
先给出做法:
while(x <= N){//这里, N 表示原数组的大小
c[x] += k;
x += lowbit(x);
}
证明其正确性:
当 \(a_x\) 修改时,我们维护的 \(c\) 数组也要相应的修改——要且仅要修改那些包含了 \(a_x\) 的 \(c_i\). 现在,问题转化成了,我们怎样找全包含了 \(a_x\) 的 \(c_i\) 呢?
首先,我们约定,“\(c_n\) 覆盖 \(c_m\) ” 表示 \(c_m\) 所维护的区间 \(\in\) \(c_n\) 所维护的区间。例如,\(c_8(它维护 a_8+a_7+a_6+a_5)\) 覆盖 \(c_6(它维护a_6+a_5)\).
- 性质 \(1\):若 \(c_a\) 覆盖 \(c_b\), 且 \(c_b\) 覆盖 \(c_n\), 那么 \(c_a\) 覆盖 \(c_n\)。\((a \ne b \ne n)\)(这是比较显然的,结合“覆盖”的定义不难想明白)
- 命题 \(1: c[x+lowbit(x)]\) 覆盖 \(c[x]\)
证明:
首先,不难得出,\(lowbit(x+lowbit(x)) > lowbit(x)\).
\(\Rightarrow lowbit(x+lowbit(x)) \ge lowbit(x) \times 2\)
设 \(lowbit(x+lowbit(x))=lowbit(x)\times2+k\) \((k\ge0)\)
\(c[x]=a[x]+...+a[x-lowbit(x)+1]\)
\(c[x+lowbit(x)]=a[x+lowbit(x)]+...+a[a-lowbit(x)+1-k]\)
显然,\(c[x+lowbit(x)]\) 覆盖 \(c[x]\). - 命题 \(2\) : 若 \(x < y < x+lowbit(x)\) \((lowbit(x)>1)\), 则 \(c[y] \cap c[x]=\varnothing\)(这里,\(c[y] \cap c[x]\) 表示 \(c[y]\) 与 \(c[x]\) 所维护的区间的交集)
证明:
设 \(y=x+b\). 那么,有 \(1 \le b < lowbit(x)\), 同时,可知 \(lowbit(y)=lowbit(b)\) (这里需要想一想,不难).
考察 \(c[y]\) 所维护的区间的最左端 \(y-lowbit(y)+1\). \(y-lowbit(y)+1=x+b-lowbit(b)+1 > x\)
所以,\(c[y] \cap c[x]=\varnothing\)
有了以上的命题和性质,前面单点修改的代码的正确性,也就不难证明了。
当问题可以转化为对一个序列进行单点修改,区间查询时,我们就可以去维护树状数组而非直接维护这个序列。

浙公网安备 33010602011771号