• [ZKW线段树]


    ZKW线段树


    今天终于把ZKW线段树搞出来了,也算还了一个欠账。

    参考:统计的力量(清华大学张昆玮)

    首先,我相信大家都会常规的线段树,也就是递归版的线段树,并且,也会简单的树状数组。
    我们知道,树状数组和线段树时间复杂度都是(O(nlog_n)),但是树状数组要快上不少,究其根本,是因为树状数组在实现上是非递归,而常规线段树是递归的。所以我们考虑使用非递归手段来实现线段树,这就是ZKW线段树。

    要实现非递归,自上而下很难想出来,所以我们可以考虑自下而上地实现线段树。
    按照线段树的建树方法,我们可以发现,节点 u 的父亲节点就是 u>>1, 而它的兄弟节点则为 u^1。
    我们先把要处理的区间(无论是修改还是查询)由闭区间改为开区间。这样如果区间的左端点是其父亲的左儿子(~l & 1), 那么其兄弟节点必定在待处理的区间内部,同理,如果右端点是其父亲节点的右儿子(r & 1), 那么其兄弟节点也必定在待处理区间内。当左端点和右端点是兄弟节点时(l ^ r ^ 1),则处理结束。
    以上这些判断都可以使用位运算来加速。
    以上这些,看一看,想一想就足以用来解决单点修改区间查询一类问题。对于区间修改单点查询这一类问题,用差分也可以很快搞出来。

    但是,在处理区间修改区间查询这一类问题时,和常规线段树相同,我们需要维护lazy标记。但是,怎么下放标记? 答案是不用下放,直接永久化标记!!!

    但是此处有一个坑点,如果标记在上面,而查询时在标记下方就结束,那么答案就会少算,怎么办?那就一直更新到根节点,用lazy标记乘上子节点个数即可。


    下面用一道区间修改区间查询求和的裸题来作为例题,还不懂的可以看代码来帮助理解
    codevs1082

    代码

    #include <cstdio>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    typedef long long LL;
    
    const int maxn = 200000 + 5;
    int n, M;
    LL sum[maxn << 2], add[maxn << 2];
    
    void build() {
        M = 1;
        while(M < (n + 2)) M <<= 1;
        for (int i = 1; i <= n; i++) {
            int x = M + i;
            scanf("%lld", sum + x);
            while(x >>= 1) sum[x] = sum[x << 1] + sum[x << 1 | 1];
        }
    }
    
    void update(int l, int r, LL x) {
        l += M - 1, r += M + 1;
        int L = 0, R = 0;
        for (int i = 1; l ^ r ^ 1; i <<= 1, l >>= 1, r >>= 1) {
        	sum[l] += L * x, sum[r] += R * x;
            if(~l & 1) add[l ^ 1] += x, sum[l ^ 1] += i * x, L += i;
            if(r & 1)  add[r ^ 1] += x, sum[r ^ 1] += i * x, R += i;
        }
       	sum[l] += L * x, sum[r] += R * x;
        while(l >>= 1) sum[l] += x * (L + R);
    }
    
    LL query(int l, int r) {
        l += M - 1, r += M + 1;
        LL ans = 0;
        int L = 0, R = 0;
        for (int i = 1; l ^ r ^ 1; i <<= 1, l >>= 1, r >>= 1) {
            ans += add[l] * L + add[r] * R;
            if(~l & 1) ans += sum[l ^ 1], L += i;
            if(r & 1)  ans += sum[r ^ 1], R += i;
        }
        ans += add[l] * L + add[r] * R;
        while(l >>= 1) ans += add[l] * (L + R);
        return ans;
    }
    
    int main() {
        scanf("%d", &n);
        build();
        int q;
        scanf("%d", &q);
        while(q--) {
            int op, a, b, c;
            scanf("%d%d%d", &op, &a, &b);
            if(op == 1) {
                scanf("%d", &c);
                update(a, b, c);
            }
            else
                printf("%lld
    ", query(a, b));
        }
        return 0;
    }
    
  • 相关阅读:
    python 访问对象的属性与方法
    python 使用类实列化对象
    python 第一个Python类(创建类)
    python 安装第三方模块
    Redux其实很简单(原理篇)
    JavaScript、ES5和ES6的介绍和区别
    Yes,I know the way to learn Ens !
    移动游戏加载性能和内存管理全解析 学习
    内存耗用:VSS/RSS/PSS/USS
    Android性能测试-内存
  • 原文地址:https://www.cnblogs.com/ZegWe/p/6139364.html
Copyright © 2020-2023  润新知