传闻中是一个清华大学的巨佬所发明,本质上是经典的线段树区间划分思想,采用了自底向上的方式传递区间信息,避免的递归结构,其代码相对经典线段树更简单,常数更小,易于实现。
首先来说说它的构造吧。
这就是两种不同的线段树,前一种是普通线段树,后一种是zkw线段树,这样看起来是不是zkw还更麻烦呢?但是这只是一种假象,正常的线段树还需要开辟储存左右节点的空间,所以要乘以4倍来记,那么按照上图其实zkw线段树还是对于空间的需要少多了。
规律是很显然的:
- 一个节点的父节点是这个数左移1,这个位运算就是低位舍弃,所有数字左移一位
- 一个节点的子节点是这个数右移1,是左节点,右移1+1是右节点
- 同一层的节点是依次递增的,第n层有2^(n-1)个节点
- 最后一层有多少节点,值域就是多少(这个很重要)
这样我们就可以开始建树了,首先我们要开辟空间,为了使得我们开批的空间能够满足使用,由上面的规律可以得到我们所需要开辟的空间就是最后一层的2n个,但是如果最后一层不满足2的n次方,那我们就把它补到2的n次方就行了,所以我们需要的空间就是大于最后一层且最小的2n空间。
void build()
{
for(m=1;m<n;m<<=1);
for(int i=m+1;i<=m+n;i++)
{
cin>>d[i];
}
for(int i=m-1;i;--i)
{
d[i]=d[i*2]+d[i*2+1];
}
}
创建空间完成之后,我们就可以将初值插入刚才创建的空间之后进行,然后用一个循环遍历一遍m,将其的左右的节点的数值加上,那么这样一棵树就建好了。
下面就是单点修改了,单点修改其实特别特别简单,直接将要加的点加到我们开辟空间之后的那个点,然后用循环来得到父节点,然后让父节点加上其左右节点的值就可以了。
void add(int a,int k)
{
d[a=m+a]+=k;
while(a)
{
d[a>>=1]=d[a<<1]+d[a<<1'1];
}
}
最难的还真就是去理解二进制的,其实本身思路是不难的。
最后就是区间查询的操作
int dp(int l,int r)
{
int ans=0;
for(l=l+m-1,r=m+r+1;l^r^1;l>>=1,r>>=1)
{
if(~l&1)ans+=d[l^1];
if(r&1)ans+=d[r^1];
}
return ans;
}
是不是发现一脸懵逼?是不是觉得这玩意是啥?我一开始看也是这样的,其实我们需要将[5,6]转化为开区间(4,7)来进行运算,因为当其为4,7的时候,我们可以先判断它的左节点4的兄弟是否是右儿子 ,如果是右儿子的那么肯定是在区间之内的,同理7的时候就可以判断是否为左儿子,同样会在区间之内,然后每次将其往上循环一层,最后判断r和l是否为兄弟节点,因为如果是兄弟节点就代表了查询已经结束,可以进行输出了
a<<1 | a乘以二 |
a>>1 | a除以二 |
r&1 | 判断是否是右节点 因为右节点为奇数,与1相同就为真 |
~l&1 | 判断是否是左节点 ,与上基本同理 |
l^1 | l的兄弟节点 |
l^r^1 | l和r是否为兄弟节点 |
所实话这个算法确实有方便的地方,但二进制操做实在是太多了导致不好理解。