[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\n", query(a, b));
    }
    return 0;
}
posted @ 2016-12-06 22:21  ZegWe  阅读(589)  评论(0编辑  收藏  举报