• [学习笔记] zkw 线段树


    建树

    对于维护长度为 (n) 的序列,底层至少 (n+2) 个点。(t_{N+i}) 就代表下标 (i) 对应的叶子节点。底层的第一个节点的编号就是 (N),它是一个虚点。

    计算一下可知 (N) 最多增加到 (2n+2),由于我们需要用到 (t_{N+n+1})(为什么后面再说),所以大约开三倍空间即可。

    for(N=1;N<n+2;N<<=1);
    for(int i=1;i<=n;++i)
        t[N+i]=read(9);
    for(int i=N-1;i;--i) // 注意从 N-1 开始更新,否则会越界
        t[i]=t[i<<1]+t[i<<1|1];
    

    单点修改,区间查询

    void change(int o,int k) {
        for(t[N+o]=k,o=N+o>>1;o;o>>=1)
            t[o]=t[o<<1]+t[o<<1|1];
    }
    

    建立两个哨兵节点 (l,r) 代表区间 ([L,R])(L-1,R+1) 两个点。两个哨兵同时上移,如果 (l) 是左儿子,就将它对应的右儿子加入贡献。(r) 同理。

    由于 ([L,R]) 可能取到 ([1,n]),所以 (t_N,t_{N+n+1}) 这两个虚点是必要的。

    int ask(int l,int r) {
        int ret=0;
        for(l=N+l-1,r=N+r+1;l^r^1;l>>=1,r>>=1) {
            if(~l&1) ret+=t[l^1];
            if(r&1) ret+=t[r^1];
        }
        return ret;
    }
    

    区间修改,区间查询

    先放一张图吧:

    懒标记

    维护 (t) 数组为区间和,(la) 数组是可持久化懒标记。

    统计 (ls,rs) 分别为 (l,r) 向上跳时经过的 区间内 的点的个数,( ext{all}) 维护 (l,r) 跳跃当前层的节点个数。 当 (l,r) 成为某点的左右儿子时,也要继续往上更新。

    void modify(int l,int r,int k) {
        int ls=0,rs=0,all=1;
        for(l=N+l-1,r=N+r+1;l^r^1;l>>=1,r>>=1,all<<=1) {
            t[l]+=k*ls,t[r]+=k*rs; // 此时的 ls,rs 已经是 l,r 的儿子部分了
            if(~l&1) {
                la[l^1]+=k; t[l^1]+=k*all;
                ls+=all;
            }
            if(r&1) {
                la[r^1]+=k; t[r^1]+=k*all;
                rs+=all;
            }
        }
        for(;l;l>>=1,r>>=1) 
            t[l]+=k*ls,t[r]+=k*rs;
    }
    

    询问同理。对于不查询完整区间的,用懒标记贡献;反之直接加上 (t) 数组即可。

    int ask(int l,int r) {
    	int ls=0,rs=0,all=1,ret=0;
        for(l=N+l-1,r=N+r+1;l^r^1;l>>=1,r>>=1,all<<=1) {
        	ret+=la[l]*ls+la[r]*rs;
            if(~l&1) ret+=t[l^1],ls+=all;
            if(r&1) ret+=t[r^1],rs+=all;
        }
        for(;l;l>>=1,r>>=1) 
            ret+=la[l]*ls+la[r]*rs;
        return ret;
    }
    

    差分

    看了半天没有懂,于是就 咕咕咕 了。

    区间修改,单点查询

    此时将原数组当作懒标记数组来使用即可。代码十分简单。需要注意的是修改时,在 (l,r) 成为某点的左右儿子后就不再修改了。画画图可以知道此时已经将所修改区间到根的路径都打上了懒标记。

    void modify(int l,int r,int k) {
        for(l=N+l-1,r=N+r+1;l^r^1;l>>=1,r>>=1) {
            if(~l&1) t[l^1]+=k;
            if(r&1) t[r^1]+=k;
        }
    }
    

    查询时将叶子到根的路径上懒标记累加即可。

    int ask(int x) {
        int ret=0;
        for(x=N+x;x;x>>=1) ret+=t[x];
        return ret;
    }
    
  • 相关阅读:
    多线程
    序列化
    IO流
    递归
    JAVA异常处理
    java常用类-StringBuffer,Integer,Character
    系统测试过程
    备份,健壮,文档,在线帮助,网络,稳定性测试
    异常测试/恢复性测试(可靠)
    配置测试
  • 原文地址:https://www.cnblogs.com/AWhiteWall/p/15168467.html
Copyright © 2020-2023  润新知