zkw线段树

传闻中是一个清华大学的巨佬所发明,本质上是经典的线段树区间划分思想,采用了自底向上的方式传递区间信息,避免的递归结构,其代码相对经典线段树更简单,常数更小,易于实现。
首先来说说它的构造吧。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6FSiNoFa-1596118130516)(1452724-20180821160156936-1884947255.bmp)]

这就是两种不同的线段树,前一种是普通线段树,后一种是zkw线段树,这样看起来是不是zkw还更麻烦呢?但是这只是一种假象,正常的线段树还需要开辟储存左右节点的空间,所以要乘以4倍来记,那么按照上图其实zkw线段树还是对于空间的需要少多了。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lDpgoNAq-1596118130522)(1452724-20180821223038494-1334459442.bmp)]

规律是很显然的:

  • 一个节点的父节点是这个数左移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<<1a乘以二
a>>1a除以二
r&1判断是否是右节点 因为右节点为奇数,与1相同就为真
~l&1 判断是否是左节点 ,与上基本同理
l^1l的兄弟节点
l^r^1l和r是否为兄弟节点

所实话这个算法确实有方便的地方,但二进制操做实在是太多了导致不好理解。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值