专栏文章
一种支持单点修改、区间查询非可减性信息的树状数组
算法·理论参与者 2已保存评论 1
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 1 条
- 当前快照
- 1 份
- 快照标识符
- @miqwlfkr
- 此快照首次捕获于
- 2025/12/04 11:56 3 个月前
- 此快照最后确认于
- 2025/12/04 11:56 3 个月前
0 引入
注意到树状数组可以支持单点加、区间求和,但其做法是转化为前缀和的差。
如果遇到求区间最大值、区间 GCD 这类非可减性信息时,普通树状数组就很难胜任。
这时,人们往往会直接考虑线段树这个几乎万能的 DS。
但是树状数组真的不能求区间最值吗?
1 尝试
其实已经有过 的做法了:树状数组进阶。
但研究一下就会发现,这实际上就是分成 段子区间后分别查询,且每段都是 的时间,特别暴力。
有感觉这并不是树状树组的极限。
2 改进
2.1 分析
看一张借来的图@逆流之时:

其中实线为树状数组所维护的区间,虚线为剩下的区间。
Wow!这张图简直就是线段树( 时)!
而我们知道,线段树是支持 的区间最值的。
那我们把剩下的补出来不就好了吗?
将剩下的一些区间拎出来看看:
看着是有什么规律,但说不出来……
话说树状数组的构建与
lowbit 有关,那它的互补部分呢?我们把它们写成左开右闭的形式:
发现了吧,互补区间 中 。
这是和原区间 中 很相似的(好吧本来就是对称的)。
真是妙哉!
ps:其实互补区间和求后缀的树状数组就只有 的差别。
2.2 构建
对于单点修改
我们直接上代码:
Cinline void upd(int x, const int &v) {
//其中L是互补区间,R是原区间
for (int l = x - 1; l; l -= lowbit(l)) cmax(L[l], v);
//因为互补区间是(l,r],所以l得是x-1
for (int r = x; r < N; r += lowbit(r)) cmax(R[r], v);
}
简直和原来的代码一模一样。
证明:直接用对称性或看图易证 。
对于区间最值
有一个很简单的想法:
- 首先 。
- 不断 ,直到 。
- 不断 ,直到 。
这样能保证统计的区间都是合法的,但如何才能不重不漏呢?
考虑到原区间和互补区间都是左开右闭的,所以当 时刚好统计完全。
但如何保证操作过程中一定存在某个时刻 呢?
证明:考虑 和 二进制下最高的不相同位(公共前缀后一位),显然如果 则 的这位为 , 的这位为 ;
- 若 后面的位都为 :则 会减到和 相等。
- 否则: 会减到这位后面都为 , 会加到这位变为 。
综上:一定存在某个时刻达到 。
由此,给出代码:
Cinline int query(int l, int r) {
int res = 0;
if (--l) for (; l + lowbit(l) <= r; l += lowbit(l)) cmax(res, L[l]);
//l==0时特判
for (; l ^ r; r -= lowbit(r)) cmax(res, R[r]);
//直到l==r
return res;
}
3 完整实现
Cnamespace BIT {
int L[N], R[N];
#define lowbit(x) ((x) & -(x))
inline void upd(int x, const int &v) {
for (int l = x - 1; l; l -= lowbit(l)) cmax(L[l], v);
for (int r = x; r < N; r += lowbit(r)) cmax(R[r], v);
}
inline int query(int l, int r) {
int res = 0;
if (--l) for (; l + lowbit(l) <= r; l += lowbit(l)) cmax(res, L[l]);
for (; l ^ r; r -= lowbit(r)) cmax(res, R[r]);
return res;
}
}
using namespace BIT;
显然这是很短的。
4 与 zkw 线段树的比较
4.1 从结构上看
这种树状数组同样是自底向上构建的。
这样构建出的节点是与 zkw 线段树完全一样的。
两者的区别在于不同的标号方式。
构建 zkw 线段树时一般会将 补齐至 的整数幂,以形成完全二叉树的树型,而树的右半边显然有部分节点是冗余的。
而得益于树状数组独特的标号方式,我们可以只用恰好 的节点,去掉了不必要的部分。
但注意到:《学习如何使用线段树》中有写法是不用补全的,这样两者就效果完全一样了。
4.2 从时间上看
由于上文提到,两者的结构是完全一样的。
考虑到两者使用时都没有冗余操作,因此两者的时间复杂度一定是相等的。
即修改 时花费 次操作。
4.3 综上
这个结构对 zkw 线段树并没有较大的优势,在线 zkw 线段树。
实测也证实了两者在常数上并没有大的区别。
然而 zkw 线段树还有许多更高级的使用,在这方面树状数组又受到其特殊标号方式的影响,较难做出拓展(至少目前没有)。
5 后记
这个算法被我用来优化 [NOIP2024T4] 树上查询,效果不错(至少没挂)。
另:对于此结构的更多想法(如区间 tag 修改上的推广),欢迎交流!
感谢审核,辛苦 : ) 。
相关推荐
评论
共 1 条评论,欢迎与作者交流。
正在加载评论...