zkw线段树

统计的力量

预备知识

简而言之,zkw线段树是一种非递归的线段树,相较普通线段树常数要小。
并且,zkw线段树是自底向上实现的,这与普通线段树有很大不同。
为了实现非递归,zkw线段树强行将节点个数设置为2的幂,这样就可以实现堆式的储存。
所谓的堆式储存,就是对于一个节点,它的儿子是 n2 n2+1 ,它的父亲是 n2 ,这样就可以很方便的检索。
叶子节点的编号是连续的,所以可以直接检索到原数组中的每个元素在树上的位置。
zkw线段树_第1张图片
比如在这个图中,原数组下标为一的点对应的是9(并非8,因为要预留一个位置,如果原数组从0开始,那么就把所有下标向后移动一位)。
当然也要在最后面预留一个位置,所以原数组中最后一个元素在14这个位置。

单点更新,区间求和

查询

如果查询 [a,b] 这个区间,那么相当与查询 (a1,b+1) ,注意在zkw线段树上的所有区间询问都是开区间
比如对于刚才给出的线段树,如果查询 [9,12] ,那么左指针从8出发,右指针从13出发,不断向上到父亲结点,直到成为兄弟节点
在向上跳的过程中,如果左指针是父亲的左节点,那么就要把的右兄弟的值加过来,如果右指针是父亲的右儿子,那么把左兄弟的值加进来。
如图所示,线条表示更新答案的结点。
zkw线段树_第2张图片
可以发现,区间[9,12]中所有的点都被包含进答案中了。
可以发现,正是这种查询方式,才使得查询的区间必须是开区间。

更新

对于更新貌似还是比较好想的。
只要一直向父亲跳,然后更新父亲的值。

代码

inline void query(int a,int b){
    int res=0;
    for(a=a+bas-1,b=b+bas+1;a^b^1;a>>=1,b>>=1){
        if((~a)&1) res+=tree[a^1];
        if(b&1) res+=tree[b^1];
    }
    pt(res);
}
inline void update(int a,int b){
    a=a+bas;
    for(int add=b-tree[a];a;a>>=1) tree[a]+=add;
}

区间更新,区间最值

这一个方面,我得到的结论和ppt中不同,不知道是不是理解不同
其实是可以达标记,然后每次查询是先到父亲结点把标记down下来,然后再查询。
但是这样做不好。
于是ppt中就给出了一个非常机智的做法——差分。
在这种方法里,不在需要标记,标记和值已经融为一体。
对于一个节点存的不再是最大值和标记了,只存一个东西,就是当前节点的值和父亲的差。一个节点的权值就是从它一直到根节点所有节点的权值和。
这样有什么好处呢?好处就在于,如果区间更新的话,如果把当前节点的权值加了,那么它的子孙都会收到影响,这不就完成了区间更新吗?

更新

这个要先讲更新了。
假设线段树本来是张这样的。
zkw线段树_第3张图片
对应的zkw线段树节点应该是这样的。
zkw线段树_第4张图片
那么如果给左儿子加上b,那么。
原树变成了:
zkw线段树_第5张图片
由于要维护线段树的值,那么现在的zkw线段树是这样的:
zkw线段树_第6张图片
所以,假定现在在x这个节点,更新之后,令A=max(tree[lson],tree[rson]),那么tree[lson]和tree[rson]都要减去A,而根节点则加上A。
注意,更新时要一直更新到根节点。

查询

对于一个节点,他所代表的区间中的最大值就是当前点到根节点的所有点的权值和,所以查询和之前的区间和大同小异,不同的是这次更新答案是取max或者min,而并非加起来。
注意,即使是在两个节点成为兄弟之后,也要一直走到根节点。

代码

void update(int L,int R,int add){
    for(L=bas+L-1,R=bas+R+1;L^R^1;L>>=1,R>>=1){
        if(~L&1) tree[L^1]+=add;
        if(R&1) tree[R^1]+=add;
        up(L);up(R);
    }
    while(L>1) up(L),L>>=1;
}
void query(int L,int R){
    int Lans=-INF,Rans=-INF;
    for(L=bas+L-1,R=bas+R+1;L^R^1;L>>=1,R>>=1){
        Lans+=tree[L];Rans+=tree[R];
        if(~L&1) Lans=max(Lans,tree[L^1]);
        if(R&1) Rans=max(Rans,tree[R^1]);
    }
    Lans+=tree[L];Rans+=tree[R];
    int res=max(Lans,Rans);
    while(L>1) res+=tree[L>>=1];
    pt(res);
}

还有一个问题

如何选取树的大小(也就是代码中的bas)?
ppt上说至少要取到n+2。也就是说,一棵能放 [0,2x) 的树,只可以检索 [1,2x2]
比如1023的话,总区间就是[0,2048)。
我一开始还心有疑虑,因为好像n+1这个点不会被访问到。
但是这并不是访问到与否的问题。在zkw线段树中一定要保证 n2 是父亲结点,n^1是兄弟节点。
如果在1023的数据范围下,仍然把总区间设成[0,1024),那么叶节点的编号就是[1024,2048),那么n+1对应的编号就是2048,那么按照规则,它的父亲是1024,然而1024确实一个叶子节点。
所以确定bas时:

while(bas2) bas<<=1;

最后一个问题

在区间更新,区间最值这里,可以选择一个一个把元素插入树中,或者可以先读入,然后build一下。
貌似build比较快。

void build(int n){
    for(int i=bas-1;i>=1;i--)
        tree[i]=max(tree[i<<1|1],tree[i<<1]);
    for(int i=bas+n+1;i>=2;i--)
        tree[i]-=tree[i>>1];
}

你可能感兴趣的:(数据结构)