树状数组进阶
树状数组是众多数据结构中常数较小,简单好写的,很多 ds 题都应该优先考虑树状数组。
接下来介绍树状数组的几种常见套路。
-
单点加,区间求和
-
区间加,单点查询
-
区间加,区间求和
-
二维树状数组,包括上面 \(3\) 个操作
-
单点修改,求全局 \(k\) 小值
-
求前驱,后继,排名等平衡树操作
单点加,区间求和
这是 $单点修改,区间求前缀$ 的模板
int lowbit(int x) { return x & (-x); }
void add(int x, int k)
{
for (int i = x; i <= n; i += lowbit(i)) tr[i] += k;
}
int query(int x)
{
int res = 0;
for (int i = x; i; i -= lowbit(i)) res += tr[i];
return res;
}
区间前缀和作差即可。
区间加,单点查询
考虑维护差分数组,求和时求的是差分数组,最后加上这个数原来的值即可。
区间加,区间求和
还是考虑差分数组。
记差分数组为 \(c_i\) ,原数组为 \(a_i\) ,则 \(a_i = a_{i-1} + c_i\) 。
而我们不能够通过 \(\sum c_i\) 推出 \(\sum c_i \times i\) ,那么每次修改分别维护 \(\sum c_i\) 和 \(\sum c_i \times i\) 即可。
二维树状数组
其实是树状数组套树状数组,每行中 \(tr_n\) 的管辖范围是 \(\sum _ {i = n - lowbit(n) + 1} ^ n a_n\) 而行与行之间还有额外的管辖范围,不多赘述。
单点修改,求全局 \(k\) 小值
这里由于是全局,所以可以使用权值树状数组,值域过大的话离散化即可。
考虑倍增枚举区间长度,当前右端点 \(x\) 加上 \(2 ^ i\) ,若小于等于 \(x\) 的数超过或者等于 \(k\) ,就不加,否则 \(x\) 加上 \(2^i\) , \(sum\) 加上 \(tr_{x + 2 ^ i}\) 。
\(i\) 从 \(log_2 n\) 枚举到 \(0\) , \(x\) 和 \(sum\) 初始都为 \(0\) ,最后答案就是 \(x + 1\) 。
求前驱,后继,排名等平衡树操作
求排名,就是求出 \((\sum _ {i = 1} ^ {x - 1} tr_i) + 1\)
求前驱,可以先把 \(x\) 的排名算出来,再用刚才求 \(k\) 小值的方法求出 \(x\) 最后一个数量不为 \(0\) 的数。
求后继,还是先求 \(x\) 的排名,再求出第一个大于 \(x\) 且数量不为 \(0\) 的数。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】