基础线段树·修改版
来源:互联网 发布:台湾观光局数据 编辑:程序博客网 时间: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 函数内, 也就操作只需要写一次。
两个版本的不同:
注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;}
我是蒟蒻,请多多指教
- 基础线段树·修改版
- 基础线段树的 修改与查询
- HDU1754 线段树 单点修改 最大值(基础题)
- POJ 3468 线段树 区间修改 基础题
- 线段树区间修改
- 线段树(单点修改)
- 线段树的修改
- 线段树的修改
- [线段树]分数修改
- 线段树区间修改
- 线段树的修改
- 线段树区间修改
- 线段树基础
- 基础线段树
- 线段树基础题
- 线段树 基础
- 线段树基础篇
- POJ2528线段树基础
- 代理模式(Proxy Pattern)
- 【南阳oj 108士兵杀敌(一)】 (线段树 模板题)
- 高效算法设计(二分查找,范围统计)
- 8月12日训练日记
- IO流学习-03
- 基础线段树·修改版
- 极验验证码破解(二)
- IOS逆向之汇编语言程序入门
- 2.常用控件:ListView
- ACM第三次比赛题目及标准程序(贪心)
- 《剑指offer》牛客网java题解-从尾到头打印链表
- 循环——的斐波那契数列——虽然听懂了,但直接听答案果然效果不好,忘得真快
- Effective C++学习心得一
- JVM那些事儿之执行原理(二)