初次入门——ZKW线段树

今天老蔡教了个高大上的ZKW线段树,于是我突发奇想想来写篇博客,算是对自己加深下理解
才刚入门,所以就只讲一些入门的,各位巨巨不要嘲笑俺 初次入门——ZKW线段树 - 深海灬孤独 - Alex

  这个线段树不需递归,顺序是自底向上,由于信息总是存在叶子节点,光这样也还是不够的,由于数据在原数组和树上存的位置是有一些偏差的,所以这里就需要计算一个整体偏移量M,大家来看这个图

初次入门——ZKW线段树_第1张图片
                                   图1
   我们发现,最下面的节点为4,5,6,7,那么对应原数组(假设原数组下标从1开始)直接下标减掉个3不就好了,这样就可以直接找到叶子节点了,而不需要像常规线段树那样还要从上到下去递归寻找。
  还有,第k层的第一个数,它总是为2^(k-1),我们按这个来寻找M
     for(M=1;M<=n+1;M*=2);
至于这里为什么是<=n+1,是这样的,理论上第k层可以存的数的个数是2^k-1,然而对于这里的话,我们必须把底层最两端的叶子节点牺牲掉,也就是不存任何信息(至于为什么等等再说),也就是说,实际上底层只存了n-2个数,那么,如果我要存n-1个数,岂不是不够?,为了避免这一点,所以我们要多计算一层M。
   还有,在查询的时候,例如查询[s,t],切记要执行s+=M-1,t+=M+1;为什么?
如果不执行的话,是有可能把某些节点遗漏的,比如下图
初次入门——ZKW线段树_第2张图片
                          图2
如果不执行 s+=M-1,t+=M+1,那么其实s,t这两个节点是被忽略的,这就是为什么要拓展空间(为什么是这样往上走的等等再讲)
现在可以解释下为什么要牺牲最两端的叶子节点
还是看上图,坐标是8,9,10,...,15,我们来查询[9,14],执行 s+=M-1,t+=M+1,是不是成了(8,15),正好铺满了,如果不牺牲这连个点,是不是会使得s,t不在同一层上呢?
对了,既然建树是自底向上的,那么具体是怎么样的呢,刚才讲到算好M
接下来,不就是把信息存在叶子,然后我们得从叶子开始往上走,
例如是算区间和的:
  for(i=M-1;i;i--)
        tree[i]=tree[i<<1]+tree[i<<1|1];
 M-1,相当于从上一层的最后一个节点开始,然后把两个子节点的信息加起来保存在当前节点。
建树好了,那么更新呢?
更新就太简单了,O(1)时间找到子节点,然后修改,然后就去修改祖先就行了(不要忘了位置的偏移)
for(tree[p+=M]+=val,p>>=1;p;p>>=1)
        tree[p]=tree[p<<1]+tree[p<<1|1];

下面是查询
  for(s+=M-1,t+=M+1;s^t^1;s>>=1,t>>=1)
    {
        if(~s&1)
            ans+=tree[s^1];
        if(t&1)
            ans+=tree[t^1];
    }
下面解释下这段代码
  s^t^1,看图
初次入门——ZKW线段树_第3张图片
 倒数第二层的两个黑点,相差1,显然现在已经把[s,t]的范围包含了,也就是说程序到这可以停止了,那么s^t^1,当s==t-1时,
是不是就是0呢?

        if(~s&1)
            ans+=tree[s^1];
        if(t&1)
            ans+=tree[t^1];

~s&1可以来判断s是不是左节点,t&1来判断是不是有节点,大家试着把1,2,3,4....改成1,10,11,试试
会不会发现,每个节点的两个子节点都是以当前节点为前缀的,而左节点的最后一位是0,右节点的最后一位为1
然后,为什么是 ans+=tree[s^1];和 ans+=tree[t^1];,t^1就是把t的最后一位取反,0->1,1->0,就是找点另一半,左节点的话,找右节点,反之相同,然后,为什么是要这么去查询呢,依然去看图二,我们假设现在要算[10,13],现在变成(9,14),现在s不是左节点,直接往上走,t不是右节点,直接往上走,再看这两个黑点,不正好包括了 [10,13]的范围呢?
再看图3,s是左节点,加上右节点,然后向上走,t是右节点,加上左节点,然后往上走,这样,不正好包括了范围了呢?


基本入门的就是这样,附上HDU敌兵布阵的代码

//zkw线段树

#include<stdio.h>
#include<string.h>

const int maxn=50010;
int tree[4*maxn];
int M;
void Build_Tree(int n)
{
int i;
for(M=1;M<=n+1;M*=2);
for(i=M+1;i<=M+n;i++)
scanf("%d",&tree[i]);//M到叶子节点那层,然后存数据
for(i=M-1;i;i--)
tree[i]=tree[i<<1]+tree[i<<1|1];
}

void Updata_Tree(int p,int val)
{
for(tree[p+=M]+=val,p>>=1;p;p>>=1)
tree[p]=tree[p<<1]+tree[p<<1|1];
}

int Query_Tree(int s,int t)
{
int ans=0;
for(s+=M-1,t+=M+1;s^t^1;s>>=1,t>>=1)
{
if(~s&1)
ans+=tree[s^1];
if(t&1)
ans+=tree[t^1];
}
return ans;
}

int main()
{
int T,n;
while(~scanf("%d",&T))
{
int t=0;
while(T--)
{
char op[10];
scanf("%d",&n);
Build_Tree(n);//建树
printf("Case %d:\n",++t);
while(1)
{
scanf("%s",op);
if(op[0]=='E')
break;
else if(op[0]=='Q')
{
int s,t;
scanf("%d%d",&s,&t);
printf("%d\n", Query_Tree(s,t));
}
else if(op[0]=='A')
{
int val,p;
scanf("%d%d",&p,&val);
Updata_Tree(p,val);
}
else if(op[0]=='S')
{
int val,p;
scanf("%d%d",&p,&val);
Updata_Tree(p,-val);
}
}
}
}
return 0;
}

至于是区间更新的,等我学了以后再来写吧,嘻嘻嘻

你可能感兴趣的:(初次入门——ZKW线段树)