网上流传、通用的是另一个版本,与我这一个有所不同,我自己的第一印象就是这样一种线段树,也仅仅有一点不同而已,不过,这一种,更新数据的位置只需要写一次就好,而网上那一种需要写三次。
常见版本:
修改操作时,当前区间如果在修改区间内,则对当前区间操作,并把操作加到当前区间的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;
}
我是蒟蒻,请多多指教