线段树,是在信息学各类比赛中经常出现的数据结构之一
普通的线段树大家应该都会,下面来介绍一种不需要递归的线段树
zkw线段树
出自:《统计的力量》-张昆玮
zkw线段树不同于普通线段树的地方在于:它采用堆结构,构造一颗满二叉树(也可以说是完全二叉树),而二叉树的最后一层则是各个节点。
注意:zkw线段树必须是点树,即完全闭区间
普通线段树中的修改需要去查询节点,并分为三类:
完全覆盖,在左区间,在右区间
那么换个方法,自底向上更新呢?
这就是zkw的想法
下面以 单点修改 区间求值为例子
建树:
采用堆结构,取到log(n)-1层最后一个节点,例如上图中,[3,4]节点的标号为3。
那么从3+1开始到3+4,就是1-4四个点的位置了。
好。我们直接把值丢到tree[4~7]中就可以了。。。。
同时向上更新:tree[i]=tree[i*2]+tree[i*2+1]
代码:
inline void up(int x)
{
tr[x]=tr[x<<1]+tr[x<<1|1];
}
inline void build()
{
for(M=1;M<=n+1;M<<=1);
for(int j=M+1;j<=M+n;j++)
scanf("%d",&tr[j]);
for(int j=M-1;j;j--)
up(j);
}
上面代码中,M为log(n)-1层的最后一个节点的下标
更新:
单点更新,更新第k个点同时更新所有k的祖先
inline void update(int x,int y)
{
for(tr[x+=M]+=y,x>>=1;x;x>>=1)
up(x);
}
查询:
查询s-t区间的和
这个略微复杂,考虑s和t的区间位置:
当s为左儿子,则s的父亲节点被s-t包含,反之该区间内只有s被包含同理 当t为右儿子,则t的父亲节点被s-t包含
很容易理解 可以直接加上t和s的兄弟节点来得到父亲节点的值,同时把s和t上移
当s和t是兄弟时,说明s-t的区间中所有数已经被全部包含
这里可以用到一些位运算优化:
用&确定左右子树,用^确定是否为兄弟关系
inline int query(int s,int t)
{
int ans=0;
s=s+M-1;t=t+M+1;
for(;s^t^1;s>>=1,t>>=1)
{
if(~s&1)ans+=tr[s^1];
if(t&1) ans+=tr[t^1];
}
return