一般的线段树采用的是自顶向下递归建树的办法,但这样写起来比较麻烦,于是有人就想着能不能有自底向上的建树,ZKW线段树就出现了。
基于二叉树的性质我们知道,对于一个叶子节点数为n(2的k次方)的满二叉树,它的第一个叶子节点标号同样为n,于是对于一个有m个点的区间,我们可以假设它是一个满二叉树,只不过一部分数据为0,于是我们只需要找到大于m的最小n,并顺序输入即可建树
void down(int i)//自底向上建树
{
tree[i]=tree[i>>1]+tree[i>>1|1];
}
void build(int m)
{
int n,i;
for(n=1;n<m;n<<=1); //寻找第一个叶子的位置
for(i=n+1;i<=n+m;i++) scanf("%d",&tree[i]);//这里从n+1开始输入的原因后面会提到
for(i=n-1;i>=1;i--) down(i);
}
建好树了以后,我们尝试对它进行单点跟新,同样,思路为从叶子节点开始向上跟新
void Insert(int i,int k)
{
int nood=n+i; //n为建树时所求本来第一个叶子节点的位置
tree[nood]=k;
for(nood>>=1;nood>=1;nood>>=1) down(nood);
}
最后是求和,自底向上的求和跟普通求和不太一样。思路是对于区间[l,r],判断若l为l/2的左子树,那么l+1一定在所求范围之内,对于r同理,然后不断循环向上直到l与r为兄弟节点。为了达到这个目的,我们需要将题目所求区间[l,r]转化为[l-1,r+1]进行求解,这也是为什么之前建树从n+1开始的原因,若从n开始建树当所求区间l=1时l-1会跳到上一级(求和过程可能没那么好理解自己画个图手动模拟一下会有些帮助)。
void sum(int l,int r)
{
int s=0;
l=l+m-1;
r=r+m+1;
//分别将l与r转化为l-1与r+1
for(;l^r^1;l<<=1,r<<=1) //l^r^1为判断l与r是否是兄弟节点
{
if(!l&1) //l为l/2左节点
s+=tree[l^1]; //加上l/2右节点的值
if(r&1) //r为r/2右节点
s+=tree[r^1]; //加上r/2左节点的值
}
}