zkw线段树解决区间rmq

zkw线段树具体内容请百度统计的力量(这是他讲的时候所用的ppt的名字)

今天我们就来完整的写一个zkw线段树。

正如他在ppt里讲的

* 差分是化绝对为相对的重要手段
* 标记永久化后就是值,只不过 这种 值是相对的
* 计算答案时可以利用从节点到根部的信息

在zkw树中,每个节点存的都不是最大值M[x], 而是M[x] - M[x<<1](这里M[x<<1]就是他父亲存在最大值)。这样有什么用呢?你可以试一下从某个结点开始,走到根,你会发现一路上的和加起来,就是这里存在最大值。这样,单点Query就解决了。单点更新也比较简单,就是一点一点从叶子结点处开始修改,维护这棵树作为差分树的性质。只这样看的话,并不比普通的线段树有多大优势。

所以接下来我们看区间更新和区间查询。

区间更新时,由于是整个区间都变化,这就造成什么了呢,如果有两个叶子节点都在要更新的区间里,他俩父亲相同,而且他俩也非端点,这时,由于他俩是一起更新的,你会发现这时他俩的相对差值仍为变化,而且,由于他们自身存的是差分值,而且他们的最大值也同等增加了相同值,所以这俩个节点存的值并不需要改变,需要改变的是哪里呢?就是边界节点和跟边界节点通父亲的点,这样底层的更新最多4个点。解决完底层,它的上一层也是同理,从而每层都最多只要四个点。当左右边界同父亲的时候,在往上都只要更新两个点。这时在和线段树比较的话,你会发现,更新的点的个数少了很多有木有,当更新的层数越来越深的时候,zkw树至多4k个点,而普通线段树则是2^k个点左右。这时赤裸裸的差距啊有木有。

至于区间查询,由于每个点存的内容都和它的儿子没有关系,所以某种意义上我们可以直接找到区间的边界点,然后开始一层一层向上累加去个最大or最小就可以了。但是这个找边界点的过程还会浪费点地方。不如直接就从边界的叶子节点处一层一层向上累加,每到一层,都注意去最大or最小,来维护答案。


然后就是他的ppt中的错误,第一处在add函数里,它没有上浮到根节点,这样的话单就下次查询来说,并不会造成什么(你可以试试,这是从某点一直到根的路径中的sum仍为这个点存的Max),但是,如果有了另一次更新,这时,就会出现问题(出个数据模拟一下就可以)。所以在add函数的尾部还要加一部上浮到根节点的语句。

第二处在Query函数中,如果这时查询的不是区间是点,这时会有l=r,进一次循环,你发现l=r=0,这时,很明显,函数最后的那步上浮到根节点的循环是出不去了。

第三处仍在Query函数中,那就是这时的查询不再是开区间了,而是闭区间。比如区间的左节点是某个点的右儿子,那么这时的开区间头便是某个点的左儿子。这时按照zkw的惯例,下次的开区间节点就是他的父亲了,但是由于他的父亲的儿子中有区间内的点,从而这个父亲也算区间内的点,那么这时会出现了矛盾,也就是说,本来要经历的点却没有经历。这样肯定就有问题了,所以我们只能闭区间查询,这样才不会发生上浮一层之后,点与区间的位置关系发生改变。


好了,这次的总结就差不多了。这里是cf上的一个题目,考的就是这个。底下附上我的代码,可以看一下细节部分。

#include 

#define up(i, lower, upper) for(int i = lower; i < upper; i++)
#define down(i, lower, upper) for(int i = upper-1; i >= lower; i--)

using namespace std;
typedef pair pii;
typedef pair pdd;
typedef vector vi;
typedef vector vpii;
typedef __int64 ll;
typedef unsigned __int64 ull;

const double pi = acos(-1);
const double eps = 1.0e-9;

template
inline char read(T &n){
    T x = 0, tmp = 1; char c = getchar();
    while((c < '0' || c > '9') && c != '-' && c != EOF) c = getchar();
    if(c == '-') c = getchar(), tmp = -1;
    while(c >= '0' && c <= '9') x *= 10, x += (c - '0'),c = getchar();
    n = x*tmp;
    return c;
}

template 
inline void write(T n) {
    if(n < 0) {
        putchar('-');
        n = -n;
    }
    int len = 0,data[20];
    while(n) {
        data[len++] = n%10;
        n /= 10;
    }
    if(!len) data[len++] = 0;
    while(len--) putchar(data[len]+48);
}

//---------------------------------------------------------
struct SegTree {
    ll tree[550000];
    int m;

    void build(int len) {
        m = 1;
        memset(tree, 0, sizeof tree);
        while(m-1 <= len) m*=2;
        up(i, 1, len+1) read(tree[i+m]);
        down(i, 1, m) tree[i] = min(tree[(i<<1)], tree[(i<<1)+1]);
        down(i, 1, len+m+1) tree[i] = tree[i] - tree[i>>1];
    }

    void update(int l, int r, int val) {
        ll tmp;
        for(l += m-1, r += m+1; l^r^1; l>>=1, r>>=1) {
            if(~l&1) tree[l^1]+=val;
            if(r&1) tree[r^1]+=val;
            tmp = min(tree[l], tree[l^1]), tree[l] -= tmp, tree[l^1] -= tmp, tree[l>>1] += tmp;
            tmp = min(tree[r], tree[r^1]), tree[r] -= tmp, tree[r^1] -= tmp, tree[r>>1] += tmp;
        }
        for (;l!=1;l>>=1)
            tmp = min(tree[l],tree[l^1]), tree[l] -= tmp, tree[l^1] -= tmp, tree[l>>1] += tmp;
    }

    ll query(int l, int r) {
        ll lAns = 0, rAns = 0;
        l+=m, r+=m;
        if(l != r) {
            for(; l^r^1; l>>=1, r>>=1) {
                lAns += tree[l], rAns += tree[r];
                if(~l&1) lAns = min(lAns, tree[l^1]);
                if(r&1) rAns = min(rAns, tree[r^1]);
            }
        }
        ll ans = min(lAns+tree[l], rAns+tree[r]);
        while(l > 1) ans += tree[l>>=1];
        return ans;
    }
};

SegTree a;

int main() {
    int len, n, m, l, r;
    read(len);
    a.build(len);
    read(n);
    while(n--) {
        read(l);
        if(read(r) != '\n') {
            read(m);
            if(l > r) a.update(l+1, len, m), a.update(1, r+1, m);
            else a.update(l+1, r+1, m);
        }
        else {
            if(l > r) write(min(a.query(l+1, len), a.query(1, r+1)));
            else write(a.query(l+1, r+1));
            puts("");
        }
    }
    return 0;
}


你可能感兴趣的:(codeforces,数据结构,杂物)