基础线段树·修改版

网上流传、通用的是另一个版本,与我这一个有所不同,我自己的第一印象就是这样一种线段树,也仅仅有一点不同而已,不过,这一种,更新数据的位置只需要写一次就好,而网上那一种需要写三次。

常见版本:

修改操作时,当前区间如果在修改区间内,则对当前区间操作,并把操作加到当前区间的lazy上,也就是,所有的lazy都是对他的儿子区间有效的,对自己的区间并无效。如果一个区间的lazy不为空,并且要用到儿子区间,就要把lazy标签传给儿子区间,并同时对儿子区间的数据进行更新(也就是push_down函数),因为,有lazy标签的区间一定是已经修改完成区间。 这样,在加lazy时一次操作,在push_down里对左右儿子操作,一共三处操作。
可以发现,这个版本很关键的一句话就是,有lazy标签的区间一定已经修改完成,lazy标签只对儿子区间有效。

Jianzs版本:

这个版本相对去那个版本关键的那句话的是,有lazy标签的区间一定没有修改,lazy标签只对自己区间有效。
既然所有操作都可以用lazy来表示(要不然懒惰标记怎么对儿子区间操作呢?),那么修改操作时,当前区间如果在修改区间内,则对当前区间加上lazy标签,然后立即把lazy标签传递儿子区间(也就是push_down函数)注1,在传递过程中,更新了这个区间的数据,传递lazy标签给了儿子区间,清空这个区间的lazy标签。 并且在修改操作时,只要当前区间有lazy,就要先push_down,一方面更新自己的数据,一方面传递lazy标签。 这样,所有更新数据都在push_down 函数内, 也就操作只需要写一次。

两个版本的不同:

毛老师版本Jianzs版本
思想带有lazy标签的区间一定已经修改完成。lazy只对儿子区间有效。带有lazy标签的区间一定没有修改。lazy只对当前区间有效。
push_down 位置在需要递归儿子区间时调用,在modify_tree(lson….)和modify_tree(rson….)、 query(lson…)和query(rson…)的上面即可。只要当前区间有lazy就需调用,也就是modify_tree 和 query 的第一行。注2

注1:因为如果不立即push_down,也就不更新当前区间的数据,那么当回溯时,也就不更新父亲区间的数据,那么如果下次查询父亲区间的数据,就会出现错误。

注2:如果不在第一行,那么这层函数的运行过程中,对当前区间查询的数据都是不是最新的,也就不是正确的,所以错误,结果是一个没有处理lazy的错误结果。并且,在修改时,如果当前区间不在修改区间内,则会提前return,而这个节点的兄弟节点被修改,那么回溯时父亲节点maintian就会出现错误,这也就是说,只要在push_down后调用了儿子区间,那么左右儿子必须调用push_down来更新自己的数据,以更新父亲节点的数据。 所以放在函数第一行。

推荐一个验证自己板子正确性的地方。验线段树板子

模版

/*
自己版本
如果一个节点有lazy,说明这个节点没有修改,直接标记了lazy,
应该更新这个节点,然后把lazy传给子结点。
*/

#include <iostream>
#define lson (index<<1)
#define rson (index<<1|1)
#define mid  ((l+r)>>1)
using namespace std;
const int MAXN = 1e6;

typedef long long LL;
struct node {
    LL sum, lazy;
};
node segTree[MAXN<<2];  // 必须四倍,否则可能RE
LL a[MAXN];  // 叶子节点数据
int n, m;  // n 叶子结点数量, m 操作数

// 用于维护父亲节点数据。
// 对左右儿子节点修改后,需要重新对父亲节点数据更新。
void maintain(int index) {
    segTree[index].sum = segTree[lson].sum+segTree[rson].sum;
}

void build_tree(int index, int l, int r) {
    if(l == r) {
        segTree[index].sum = a[l];
        return;
    }
    build_tree(lson, l, mid);
    build_tree(rson, mid+1, r);
    maintain(index);
}

// 主要操作, 也就是题目中对数据的操作,套用模版基本上修改这里即可
void operate(int index, int l, int r) {
    segTree[index].sum += segTree[index].lazy*(r-l+1);
}

void push_down(int index, int l, int r) {
    if(segTree[index].lazy == 0) return;
    operate(index, l, r);

    if(l != r){  // 防止叶子结点更新后,计算左右儿子越界。
        segTree[lson].lazy += segTree[index].lazy;
        segTree[rson].lazy += segTree[index].lazy;
    }
    segTree[index].lazy = 0;
}

/*
   modeify 先push_down,更新 index 的数据,
   并且,如果[l,r],如果不在修改范围[ql, qr],会调用儿子节点,
   更新儿子节点就必须把它父亲节点的lazy传下去,不然就会错误。
*/
void modify_tree(int index, int l, int r, int ql, int qr, int oper) {
    push_down(index, l, r);
    if(l > qr || r < ql) return;
    if(ql <= l && r <= qr) {
        segTree[index].lazy += oper;
        push_down(index, l, r);
        /*
        if don't push down this node, the data of the
         node won't be updated.so the data of the 
         parents of the node won't be updated.if I 
         query the interval of the node's parents, I 
         will get a wrong data.
        */
        return;
    }

    modify_tree(lson, l, mid, ql, qr, oper);
    modify_tree(rson, mid+1, r, ql, qr, oper);
    maintain(index);
    /* 这的maintian 只对oper起作用,如果只是lazy处理,已经体
    现在处理当前区间的lazy*/
}
/*
    query 先 push_down,如果 [l,r] 在 [ql, qr] 内,需要返回最新的点的数据,
    如果不在范围,需要调用儿子节点,也就需要更新儿子节点的信息,更新儿子节点
    的信息需要父亲节点的lazy。
*/
LL query(int index, int l, int r, int ql, int qr) {
    push_down(index, l, r);
    if(l > qr || r < ql) return 0;
    if(ql <= l && r <= qr) {
        return segTree[index].sum;
    }

    LL ans1 = query(lson, l, mid, ql, qr);
    LL ans2 = query(rson, mid+1, r, ql, qr);
    return ans1+ans2;
}

void init() {
    for(int i = 0; i < (MAXN<<2); i++)
        segTree[i].sum = segTree[i].lazy = 0;
}

int main() {
//    freopen("in.txt", "r", stdin);
//    freopen("segTree_me.txt", "w", stdout);

    init();

    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++) {
        scanf("%lld", &a[i]);
    }

    build_tree(1, 1, n);
    for(int i = 1; i <= m; i++) {
        int cmd, b, c, d;
        scanf("%d", &cmd);
        if(cmd == 0) {  // 区间修改
            scanf("%d%d%d", &b, &c, &d);
            modify_tree(1, 1, n, b, c, d);
        }else if(cmd == 1) {  // 区间查询
            scanf("%d%d", &b, &c);
            cout << query(1, 1, n, b, c) << endl;
        }else if(cmd == 2) {  // 单点修改
            scanf("%d%d", &b, &c);
            modify_tree(1, 1, n, b, b, c);
        }else if(cmd == 3) {  // 单点查询
            scanf("%d", &b);
            cout << query(1, 1, n, b, b) << endl;
        }
    }

    return 0;
}
/*
常见版本
如果一个节点有lazy, 说明这个节点已经修改过,应该把lazy直接传给子结点,
然后把子结点的信息更新。
*/

#include <iostream>
#define lson  (index<<1)
#define rson  (index<<1|1)
#define mid   ((l+r)>>1)
using namespace std;
const int MAXN = 1e6;

typedef long long LL;
struct node {
    LL sum, lazy;
};
node segTree[MAXN<<2];  // 必须四倍,否则可能RE
LL a[MAXN];  // 叶子节点数据
int n, m;  // n 叶子结点数量, m 操作数

// 用于维护父亲节点数据。
// 对左右儿子节点修改后,需要重新对父亲节点数据更新。
void maintain(int index) {
    segTree[index].sum = segTree[lson].sum+segTree[rson].sum;
}

void build_tree(int index, int l, int r) {
    if(l == r) {
        segTree[index].sum = a[l];
        return;
    }

    build_tree(lson, l, mid);
    build_tree(rson, mid+1, r);
    maintain(index);
}

// // 主要操作, 也就是题目中对数据的操作,套用模版基本上修改这里即可
void operate(int index, int l, int r , int oper) {
    segTree[index].sum += oper*(r-l+1);
}

//只要要调用儿子节点,就调用push_down,
void push_down(int index, int l, int r) {
    if(segTree[index].lazy == 0) return;
    segTree[lson].lazy += segTree[index].lazy;
    segTree[rson].lazy += segTree[index].lazy;

    /* 
        应该用 index 的 lazy 更新, why?
        每一个节点的lazy都是给子结点用的,本身不能用。
        在最初 modify 加 lazy 时,那个结点就已经完成了,对自
        己的更新,加了 lazy 只能给子结点用的。依次传递,所以每
        一个结点的 lazy 都是给子结点用的。
        比如说:一个 index 在一次修改时加了 lazy,它的
        parents 在另一次修改时加了另一个 lazy 。 一次对 
        parents 的 push_down 时,把 parents 的 lazy 传给
        了 index, 如果用 index 的 lazy 对 index 更新的话,
        就会把 index 的 lazy 对自己更新了一遍,在 modify 时
        的操作又做了一遍,所以是错误的。
    !WRONG
    segTree[lson].sum += segTree[lson].lazy*(mid-l+1);
    segTree[rson].sum += segTree[rson].lazy*(r-mid);
    */

//    segTree[lson].sum += segTree[index].lazy*(mid-l+1);
//    segTree[rson].sum += segTree[index].lazy*(r-mid);
    operate(lson, l, mid, segTree[index].lazy);
    operate(rson, mid+1, r, segTree[index].lazy);
    segTree[index].lazy = 0;
}

void modify_tree(int index, int l, int r, int ql, int qr, int oper) {
    if(r < ql || l > qr) return;
    if(ql <= l && r <= qr) {
//        segTree[index].sum += oper*(r-l+1);
        operate(index, l, r, oper);
        segTree[index].lazy += oper;
        return;
    }

    push_down(index, l, r);
    modify_tree(lson, l, mid, ql, qr, oper);
    modify_tree(rson, mid+1, r, ql, qr, oper);
    maintain(index);
}

LL query(int index, int l, int r, int ql, int qr) {
    if(r < ql || l > qr) return 0;
    if(ql <= l && r <= qr) {
        return segTree[index].sum;
    }

    push_down(index, l, r);
    LL ans1 = query(lson, l, mid, ql, qr);
    LL ans2 = query(rson, mid+1, r, ql, qr);
    return ans1+ans2;
}

void init() {
    for(int i = 0; i < (MAXN<<2); i++)
        segTree[i].sum = segTree[i].lazy = 0;
}

int main() {
//    freopen("in.txt", "r", stdin);
//    freopen("segTree_mg.txt", "w", stdout);

    init();

    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++) {
        scanf("%lld", &a[i]);
    }

    build_tree(1, 1, n);
    for(int i = 1; i <= m; i++) {
        int cmd, b, c, d;
        scanf("%d", &cmd);
        if(cmd == 0) {  // 区间修改
            scanf("%d%d%d", &b, &c, &d);
            modify_tree(1, 1, n, b, c, d);
        }else if(cmd == 1) {  // 区间查询
            scanf("%d%d", &b, &c);
            cout << query(1, 1, n, b, c) << endl;
        }else if(cmd == 2) {  // 单点修改
            scanf("%d%d", &b, &c);
            modify_tree(1, 1, n, b, b, c);
        }else if(cmd == 3) {  // 单点查询
            scanf("%d", &b);
            cout << query(1, 1, n, b, b) << endl;
        }
    }

    return 0;
}

我是蒟蒻,请多多指教

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值