基础线段树·修改版

来源:互联网 发布:台湾观光局数据 编辑:程序博客网 时间:2024/06/05 18:09

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

常见版本:

修改操作时,当前区间如果在修改区间内,则对当前区间操作,并把操作加到当前区间的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];  // 必须四倍,否则可能RELL 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];  // 必须四倍,否则可能RELL 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;}

我是蒟蒻,请多多指教

原创粉丝点击