浅究下 zkw 线段树
递归式线段树和 zkw 线段树一样, 都使用满二叉树, 常规的理解下递归式线段树使用的只是满二叉树常用的的节点编号规则(堆式编号,下面的所有满二叉树同样使用堆式编号),其本质是一个分治算法,并不使用完整的满二叉树;zkw 线段树则使用了更多满二叉树的结构性质, 在递归式线段树上难以使用 zkw 线段树的算法。
以下介绍 zkw 线段树。
和递归式线段树一样, 单位长度的区间由叶子节点代表。对于满二叉树, 其节点的标号是很有规律的, 如果将整颗满二叉树用作线段树,那么对于一颗深度为 \(N\) ,根节点深度为 0 的满二叉树, 对于一个单位区间 \([l,l]\), 其在满二叉树中对应的节点的编号为 \(l+2^N-1\) 。另外, 对于同一深度的节点们, 其编号在值域上是连续的。
线段树的一个精髓是对合法长度内任意长度的区间的拆分:多个叶节点被其公共祖先代表, 使得被拆分的区间拆分成 \(O(\log (序列长度))\) 个 线段树内的 区间。对于 zkw 线段树也是如此,朴素的算法即:将被拆分区间拆成满二叉树上的代表单位区间的节点,标记其, 之后不断迭代, 去掉拥有同一父亲的两个被标记节点的标记,并标记它们的父亲, 迭代无法进行时被标记的所有节点所对应的区间即是此次拆分的结果。
当然朴素算法并没有用到什么性质。有几个显然的性质:
- 当前轮迭代未被清除的标记以后也不会被清除
- 每轮迭代后新被打上标记的节点的标号在值域上是连续的
- 设此轮迭代开始时最晚被打上标记的节点的标号区间是 [l,r], 则此轮迭代中不被清除标记的节点只能是 l 同或 r(同或与异或相对)。而标记不被清除的原因也很显然:l 是其父亲的右儿子, r 是其父亲的左儿子。这个规则在 l,r 相等即极限情况下也是有效的。
所以拆分区间的算法可以转化为维护 l,r 的算法。l,r 可能的变换如下: l=l/2 or l=l/2+1
, r=r/2 or r=r/2-1
。实际上,节点标号的奇偶性是区分节点是其父亲的左节点还是右节点的一个简单方法:偶数是左节点,奇数是右节点。由此对于 l, l 为偶数时变换为 l/2, 反之变换为 l/2+1, r 相似。常用的写法是维护 l-1 和 r+1, 变换时直接除以 2,可以轻松的证明是正确的。(这个构造真的妙
所以把要维护的区间平移一下就可以愉快地写出单点加区间求和的程序了。