Code By WenJian
{作者还在理解开区间的问题,一旦有新的想法将会及时更新}
【关于ZKW线段树】
Zkw线段树是清华大学张昆玮发明非递归线段树的写法。实践证明,这种线段树常数更小,速度更快,写起来也并不复杂。
【建树】
ZKW线段树本质上就是依赖于满二叉树中父节点与子节点的编号关系。
如上图中的一个简单的满二叉树,我们可以发现如下规律:
1>父子节点编号关系: 假设父节点的编号为 n ,那么,它的两个子节点的编号就分别为 n*2(n<<1)、n*2+1(n<<1|1);
2>二叉树层数与底层叶子节点数的关系:假设这个二叉树的层数为 m ,那么,这个二叉树的底层叶子节点数(由于是满二叉树,这也就是所有的叶子节点了)就是2^(m-1),同时,我们还可以知道,所有叶子节点中编号最小的,即在这个满二叉树左下角的叶子节点的编号也为 2^(m-1);
通过以上的两大关系,我们在存储一个数组的初始数据时,就可以直接将初始数据存储在满二叉树的底层。假设数组中有 x 个元素,那么这 x 个元素在这个满二叉树中的编号就是2^(m-1)~2^(m-1)+x-1,访问起来就很方便了。
于是就有了建树代码:(其中n代表的是初始数组中的元素个数,M代表的是最底层的叶子节点个数)
inline void Build()
{
for(M=1;M<n;M<<=1) ;//由于要构建一个满二叉树,所以我们不能直接让二叉树的叶子节点数等于元素个数,M可能会大于n;本层循环使底层叶子节点数在满足“满二叉树的前提下最小”
for(int i=M;i<n+M;i++)//由于M也同样是本层最左侧叶子节点的编号,所以直接从这里开始赋值
Tree[i]=Read();
}
(许多其他的博客总是会在这里自问自答“建完了吗?没有。”,对于这种有点SB的行为,我表示无法理解)
不过确实,到目前为止,建树还未完成,我们还需要从下往上更新其它节点的值。当然,知道了父子节点编号的关系,这个操作就非常好用了。、
inline void upgrade()
{
for(int i=M-1;i;i--)
{
Tree[i]=Tree[i<<1|1]+Tree[i<<1];//维护为区间和
}
}
当然,你也可以将其维护为最大值,最小值之类的,代码都大同小异:
最大值:
Tree[i]=max(Tree[i<<1|1],Tree[i<<1]);
最小值:
Tree[i]=min(Tree[i<<1|1],Tree[i<<1]);
到目前为止,我们才算是完成了ZKW线段树的建树工作。
<ZKW线段树中的差分思想>
在建ZKW线段树的过程中,可以用的Tree[i]表示父子节点的差值,也同样可以达到存储数据的目的。
void Build(int n)
{
for(M=1;M<=n+1;M<