树状数组
【树状数组是什么】
树状数组(Binary Indexed Tree, BIT)
支持单个元素修改 和 前缀查询。
比较一下:
子段和 | 修改单个元素 | |
---|---|---|
数组 | \(O(n)\) | \(O(1)\) |
前缀和 | \(O(1)\) | \(O(n)\) |
树状数组 | \(O(\log n)\) | \(O(\log n)\) |
【树状数组的实现】
比如 \(a\) 数组有 16 个元素。
现在我们定义树状数组 \(t\) 。
\(t[i]\) 表示 \(\sum_{j=i-lowbit(i)+1}^{i}\)。
其中 \(lowbit(x)=k\) 表示 \(2^k\mid x\) 但是 \(2^{k+1}\nmid x\) ,即二进制下最低的是 1 的位 所代表的数。
形象一点:
先把所有是 16 的倍数的下标取出,它们的值是以下标结尾的 16 个元素的和。
在把 8 的倍数取出,同样,但是 16 的倍数就不管了。
循环往复,直到取完 1 的倍数。
如上图,就是 16 个元素的例子。
经过这样的处理,查询前缀和的复杂度是 \(\log\) 级别 。
\(s[x]=s[x-lowbit(x)] + t[x]\),而 \(s[x-lowbit(x)]\) 又是一个同类型但更简单的子问题,递归求和即可。
相当于把 \(x\) 二进制拆位,因为 \(x-lowbit(x)\) 就直接消去了最后一个 1,下一次的 \(lowbit()\) 就至少乘 2,所以查询前缀和的复杂度是 \(\log n\)。
单个元素修改的复杂度也是 \(\log\) 级别
假设我们要修改 \(a[num]\) 。
首先所有下标小于 \(num\) 的 \(t_i\) 一定不用修改。
然后 \(t_{num}\) 一定要修改。
再之后,如果 \(t_x\) 要修改,那么 \(t_{x+lowbit(x)}\) 也要修改。
证明:
\(t_{x+lowbit(x)}\) 的管辖范围至少是 \(t_x\) 的 2 倍。
而 \(t_x\) 的管辖范围就是 \(lowbit(x)\)。
所以 \(t_{x+lowbit(x)}\) 可以管辖 \(a[x],a[x+1],...,a[x+lowbit(x)]\),还可以管辖 \(t_x\) 的所有。
而 \(t_x\) 可以管到 \(a[num]\),所以 \(t_{x+lowbit(x)}\) 也要修改。
接着还有:除了上述元素,其他的元素都不用修改。
证明:
假设 \(t_x\) 要修改,按照方法下一个修改的应该是 \(t_{x+lowbit(x)}\)。
我们只需要证明 \(t_{x+1},t_{x+2},...,t_{x+lowbit(x)-1}\) 都不修改即可。
而因为 \(x+lowbit(x)\) 恰使管辖范围增加,所以 \(x+1,x+2,...,x+lowbit(x)-1\) 都不能使管辖范围增加。
范围不增加,终点还远了,当然管不到 \(a_{num}\)。
综上,我们只需要按照每次增加 \(lowbit(x)\) 的方式修改即可。
最后小问题:\(lowbit(x)\) 怎么算?
\(lowbit(x)=x\) & \((-x)\),按照补码算一下就证出来了。
【优缺点】
优点:
代码短、常数小。
缺点:
单点修改,可加不可减(比如求一个子段的最值,不能大减小),无法求任意子段,只能求前缀和。
【拓展】
- 树状数组优化 dp
一部分 dp 在转移的时候都是 \(dp[i]=max\{dp[j]|j\in[1,i)\}+k\) 的形式。
以前我们要用一重循环枚举 j,但是现在我们可以用树状数组快速求 \(dp[1]\) ~ \(dp[i - 1]\) 的最值,这刚好是一个前缀。
例子:最长上升子序列
先离散化。
\(dp[i]\) 表示以 \(i\) 号元素结尾的最长上升子序列长度。
当进行第 \(i\) 次循环,求出了 \(dp[i]\) 后,\(tr[a[i]]\) 和 \(dp[i]\) 取 max,再赋值到 \(tr[a[i]]\)。
这样我们要求 \(dp[i]\) 时,我们只需要求 \(tr[1]\) ~ $ tr[a[i] - 1]$ 的 \(max\) 再加一即可。
基本想法:\(dp[i]\) 表示前 \(i\) 个分成的方案数。
\(dp[i]\) = \(\sum dp[j]\),\(j\in [1,i)\),且 \(s[i]-s[j]\geq 0\),\(s[i]\) 表示 \(a[1]+a[2]+...+a[i]\)。
注意:\(s[i]\geq s[j]\),考虑树状数组。
\(s[i]\) 为位置,\(dp[i]\) 为值。