树状数组及其简单扩展
不对树状数组做详细讲解,只对应用类型做总结.
一维树状数组
单点修改,区间查询
由于树状数组维护的是前缀信息,所以区间查询直接利用前缀特性相减即可.
单点修改只需从开始位置向后更新即可.
区间修改,单点查询
树状数组只支持单点修改,那么如何处理区间修改呢?
考虑常见的区间修改转单点修改的套路:差分.
而差分数组的前 \(n\) 项和也恰好就是真正的 \(v_i\).
所有直接差分去做即可.
区间修改,区间查询
同理,先差分,差分后区间修改是没有区别的.
而这里我们发现,我们只能得到对应的单点值,却不能直接得到对应的区间和.
难道要套一种可以区间求和的数据结构嘛?答案是否定的.
我们发现,我们要求的其实是这个:
\[\sum_{i=l}^r{\sum_{j=1}^i}{d_j}\]
其中,\(d_j\) 就是差分数组.对于内层求和显然可以直接在树状数组中查询得到.
那么怎么处理外层呢?再 \(for\) 一遍是显然不可能的.
所以我们重新考虑:
令 \(sum_i\) 表示前缀和.
\[\begin{array}{c}{\operatorname{sum}[i]=\sum_{j=1}^{i} v[j]+\sum_{j=1}^{i} \operatorname{d}[j] *(i-j+1)} \\ {\operatorname{sum}[i]=\sum_{j=1}^{i} v[j]+(i+1) * \sum_{j=1}^{i}d[j]-\sum_{j=1}^{i}d[j] * j}\end{array}\]
所以只需要去维护一个原数组的前缀和,一个和之前一样的树状数组,和另一个维护\(d_j\times j\) 的树状数组即可.
二维树状数组
单点修改,矩阵查询
直接单点去修改,二维前缀和形式查询即可.
矩阵修改,单点查询
二维差分后,每次矩阵修改只需要在四个位置修改即可.
单点查询直接用二维树状数组求二维前缀和即可.
矩阵修改,矩阵查询
同样还是先二维差分,矩阵修改就直接在四个位置单点修改即可.
那么矩阵查询呢?我们又遇到了和上面同样的问题.
这次我们要求的式子变成了这样:
\[\sum_{i=1}^{n}\sum_{j=1}^{m}\sum_{i=k}^{n}\sum_{j=l}^{m}{d_{k,l}}\]
枚举次序转换一下得到:
\[\sum_{k=1}^{n}\sum_{l=1}^{m}\sum_{i=k}^{n}\sum_{j=l}^{m}d_{k,l}\]
此时,\(d_{k,l}\) 与 \(i,j\) 无关,所以上式可写成:
\[\sum_{k=1}^{n}\sum_{l=1}^{m}{d_{k,l}\times (n-k+1)\times (m-l+1)}\]
出于习惯,把它写作:
\[\sum_{i=1}^{n}\sum_{j=1}^{m}{d_{i,j}\times (n-i+1)\times (m-j+1)}\]
考虑拆项,得到:
\[\left(\sum_{i=1}^{n} \sum_{j=1}^{m} d_{i, j}\right) \times(n+1)\times (m+1)-\sum_{i=1}^{n} \sum_{j=1}^{m} i \times d_{i, j} \times(m+1)-\sum_{i=1}^{n} \sum_{j=1}^{m} j \times d_{i, j} \times(n+1)+\sum_{i=1}^{n} \sum_{j=1}^{m} i \times j \times d_{i, j}\]
于是,和上面的一维情况类似,维护四个不同的树状数组即可.
Summary
虽然有句话叫:树状数组能实现的所有操作,线段树都可以实现.
但很多时候还是树状数组更为简便,高效.
树状数组有着线段树无法比拟的高效率和低代码难度,树状数组的唯一缺点就是有些操作树状数组不能或者难以维护.
例如一维和二维情况下的区间最值问题,如果硬要用树状数组去实现,那么会多出一个 \(log\) 的复杂度,二维情况会多出两个 \(log\) 的复杂度来.
所以,能使用树状数组的情况下,不去使用线段树是一个明智的选择,毕竟树状数组的时空复杂度都要优于线段树.(除区间最值这类问题以外