zkw线段树

本文网址:https://www.cnblogs.com/zsc985246/p/16112689.html ,转载请注明出处。

zkw线段树就是非递归的线段树

我们先来看看普通的线段树:

换成二进制看看?

这种存储方式及这三条规律便是zkw线段树的核心原理

建树

我们通过上图可以发现,我们令 m=1<<log2(n),只需要从 1+m 开始遍历,到 n+m 结束,就可以成功的建树了。

接下来考虑维护其它节点的信息。可以直接从上图看出, m 其实是第一个叶子节点,而所有的非叶子节点下标都小于 m 。通过这一点,我们可以直接从 m1 遍历到 1 访问其它节点并维护信息。

void build(){//建树 
    while(m<=n)m<<=1;//1+1<<ceil(log(n)) 
    for(int i=m+1;i<=m+n;i++){
        scanf("%d",sum[i]);//这里维护区间和 
    }
    for(int i=m-1;i;i--){//遍历非叶子节点 
        sum[i]=sum[i<<1]+sum[i<<1|1];//维护 
    }
}

单点修改

第三条规律:修改第 x 个元素对应到线段树上就是修改下标为 x+m 的数。

第二条规律:我们可以用 x>>=1 找到 x 的父亲,然后维护信息。

void change_point(int x,int pos){//a[x]=pos 
    x+=m;//找对应叶子节点 
    sum[x]=pos;//更新 
    for(x>>=1;x;x>>=1){//向上更新 
        sum[x]=sum[x<<1]+sum[x<<1|1];//维护 
    }
}

区间修改(区间加法)

跟普通的线段树差别不大,也是开一个 add 数组,记录这颗子树的加法操作。

但是,我们这是非递归,所以我们需要换种方式。

首先我们先把这个闭区间 [l,r] 转换成开区间 (l1,r+1)

转换成开区间 (l1,r+1) 后,令 s=l1,t=r+1

如果 s 是它父亲的左儿子,就更新它的兄弟节点 s1,然后 s 再跳到父亲节点,重复执行此操作。

t 同理。

注意同时进行 st 的操作。

借助图理解一下(红色代表 add 需要更改):

然后对红色部分的 add 更改即可。

等等,好像没有下传标记——

实际上,我们不需要下传标记。我们可以让标记永久化。在询问时,如果此节点的 add 有值,答案加上 区间长度×add 的值 即可。

注意,当 st 的父亲相同时,虽然不继续向上跳了,但还需要继续维护 sum 的值。

void change_part(int l,int r,int pos){//a[s~t]+=pos 
    int s=l+m-1,t=r+m+1;//s和t 
    int len=1;//当前层区间包含数的个数 
    int lcnt=0;//当前s的子树中更改的数的个数 
    int rcnt=0;//当前t的子树中更改的数的个数 
    while(s^t^1){//t^1是t的兄弟节点,因为a^a=0,所以s^t^1=0代表s和t父亲相同 
        if(s&1^1){//s&1取末位(0为左儿子,1为右儿子),再^1表示兄弟节点 
            add[s^1]+=pos;//打标记 
            lcnt+=len;//更改了len个数 
        }
        if(t&1){//原理与上相同 
            add[t^1]+=pos;//打标记 
            rcnt+=len;//更改了len个数 
        }
        //维护 
        sum[s>>1]+=pos*lc;
        sum[t>>1]+=pos*rc;
        s>>=1,t>>=1;//跳到父亲节点 
        len<<=1;//包含的节点个数*2 
    }
    int cnt=r-l+1;//总共更改的数的个数 
    s>>=1;//父节点 
    while(s){
        sum[s]+=cnt*pos;//维护 
        s>>=1;//父节点 
    }
}

单点查询

这不用多说,直接返回即可。

int query_point(int x){
    x+=m;
    return sum[x];
}

区间查询

与区间修改思路相同。

主要处理在区间修改时提到的标记永久化。

由于标记是打在一颗子树上,所以整颗子树总共加了 区间长度×add 的值 。

ll query(int l,int r){//区间求和 
    ll tmp=0;//临时答案 
    int s=l+m-1,t=r+m+1;//s和t 
    int len=1;//当前层区间包含数的个数 
    int lcnt=0;//当前s的子树中更改的数的个数 
    int rcnt=0;//当前t的子树中更改的数的个数 
    while(s^t^1){
        if(s&1^1){
            tmp+=sum[s^1]+len*add[s^1];//统计 
            lcnt+=len;//更改了len个数 
        }
        if(t&1){
            tmp+=sum[t^1]+len*add[t^1];//统计 
            rcnt+=len;//更改了len个数 
        }
        s>>=1,t>>=1;
        len<<=1;
    }
    int cnt=lcnt+rcnt;//总共更改的数的个数 
    s>>=1;//父节点 
    while(s){
        tmp+=add[s]*cnt;//统计 
        s>>=1;//父节点 
    }
    return tmp;
}

zkw线段树与线段树的对比

代码长度 理解难度 时间 空间
zkw线段树 稍高
线段树 稍慢 稍大

%%%zkw !

尾声

如果你发现了问题,你可以直接回复这篇文章!

posted @   zsc985246  阅读(683)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示