简单的树状数组


前言

推荐 《算法竞赛进阶指南 —— by lydrainbowcat》, 本文有一部分受到其启发。

本文还有一部分收到 OI-wiki 的某些内容的启发, 其网址为 OI-wiki.org。

当然我不可能把所有知识来源罗列, 希望我的文字会给你带来一些启发和消遣。

基本

单单拿出树状数组来说的话, 可以说它是一个利用某些数列信息的区间可并性, 平衡数列单点修改和前缀查询时间复杂度的数据结构, 一个树状数组维护一个数列的信息。

树状数组的 最重要 的主体是数列下标(1~n), 它就是整个结构。

将一个数二进制分解: \((a)_{10} = (b_1b_2\cdots b_k)_2\) , 定义 \(lowbit[(b_1b_2\cdots b_k)_2]\) 就是一个最大的 \(2^j\) 满足 \(b_j=1, \quad \forall p\in[j+1,k], \quad b_p=0\)

在树状数组里, 一个下标 t 就代表原数列下标在 \([t-lowbit(t)+1, t]\) 之间所有数的信息的并。

所以树状数组的查询操作就很好写了, 不断减 lowbit 不断合并信息就行了。

对于单点修改, 要找出原数列中一个数能影响的树状数组中的下标, 然后逐一修改。

对于数字 \(a\)\(b\)\(a \in [b-lowbit(b)+1, b]\), 那么首先 \(a\le b\), 再次 \(b-lowbit(b)+1 \le a\), 在 \(a\le b\) 的前提下, 后一个条件要满足, 首先必须 \(lowbit(b) \ge lowbit(a)\), 再次, 从高位往低位比, b 比 a 高的第一位一定是 lowbit(b)。由此可以很轻松地证明树状数组的修改算法。

技巧

值域树状数组 kth

将问题化简为 “寻找一个最小的 x, 使得 sum[1,x] <= k”。

这个问题显然是有单调性的, 就像 倍增lca求lca的过程 一样求好了。

追究原理?挺简单的, 很多算法也有在用, 自己看一下吧。

不会爆复杂度的 clear

主要用于防止超多 clear 暴毙, 增加了查询修改的常数(只有一点点)但是使得 clear 复杂度变成 \(O(1)\)

主要是给每个节点加一个时间戳, 不符合当前时间就初始化节点。

if 语句的分支预测造成的损失可以通过三目运算符规避。

前后拼接数列同时合并树状数组

只是想随便 yy 以下,不知道有没有用。

合并复杂度必然不低于 \(O(n)\), 然而有 \(O(n)\) 建树状数组的算法……

完全不需要考虑怎么合并嘛。

分裂数列同时分裂树状数组

然而复杂度上界也是 \(O(n)\) 的, 完全没必要思考嘛。

对结构的探索—引

O(n)建树

朴素的插入要 O(n log n), 树状数组只有 O(n) 个节点。

对于一个包含了 [l,r] 区间的节点, 有 r-l+1 个节点的插入要经过它。

由于信息的可并性, 可以直接这么写:
for(int i=1;i<=n;++i) {int j=i+(i&(-i)); t[i]+=a[i]; if(j<=n)t[j]+=t[i];}

这是结构吗?

只靠 \(fa[x] = x + lowbit(x)\) 的话,这是一个分形的, 怪异的结构。

zkw线段树带来的启发

把所有 fa 去掉, 发现是一个优美的没有右儿子的线段树结构。

讲稿《统计的力量——线段树》 by zkw

zkw 线段树介绍

基本

一颗满二叉树, 根节点为 1, 对于节点 x, ls(x) = x<<1, rs(x) = x<<1|1。

把节点标号换成二进制, 这颗满二叉树就是 01 Trie, 二叉堆的经典写法用这种结构, 线段树的经典写法也是用这种结构。(我超喜欢动态开点线段树

zkw 线段树就是利用这种满二叉树的性质, 提高寻找某段区间是有哪些区间并起来的效率的算法。

找数列中的一个下标对应的叶子

预处理 可以用归纳法证明, 满二叉树的叶节点去掉 highbit, 就是对应的原数列中的下标。

对于询问区间的探索

朴素探索

对于一个询问 \([l,r]\), 它一定可以被拼出来嘛?当然可以, 用叶节点……

那么它的最简拼接是什么?

首先一个节点的右子节点一定是奇数的, 左子节点一定是偶数的。

考虑先找出 l、r 所对应的叶子节点。

考虑缩小询问区间, 若 l 为奇数, 那么显然这个 \(l\) 可以先加上, 然后缩小询问区间;同样的, 若 r 为偶数, 那么这个 r 可以先加上, 然后缩小询问区间。

推广一下, 若 l 代表的节点(不一定是叶子节点)是奇数, 那么把 l 先加上, 然后缩小询问区间, 同样的, 对 r 也有类似处理方法。

为什么可以这样做? 因为此时 l(或 r) 所代表的节点一定是最简拼接中的节点。

之后如何缩小询问区间呢?若已经加上了, 则变成父节点的兄弟, 否则变成父节点。

什么时候结束?等到 l>r 就行了。

题解:询问区间改成开区间

如果把询问的闭区间改成开区间, 然后按照闭区间的叶子节点不断往上走, 可以发现每次停顿的兄弟节点就是要找的区间节点, 这个可以很轻松证明。

迭代停止的条件也将变成两个节点变成兄弟节点

对结构的探索 —— 续

参考zkw 的讲稿吧。

小结

树状数组结构的研究可以从满二叉树的性质入手。

future

教练,我想学 leafy Tree

posted @ 2020-10-17 10:06  xwmwr  阅读(236)  评论(2编辑  收藏  举报