关于pushup与pushdown的几种常见情况
适用于线段树、平衡树等树形结构。
注意:
本文中的:
,即 。 ,即 。
一.区间修改,区间求和(求最值)( )
分两种情况。
1. 将 区间内的每一个数全部赋值为一个数 。
此时采用打懒标记(
想想怎么下传。
令此节点打上的懒标记为
将左右儿子的值以及懒标记改为
另外,总和或最值会如何改变?
,因为区间内所有数都变成了 。 ,同上。
代码如下:
inline void pushdown(int id, int tl, int tr){
if (tree[id].tag != -1) { // -1 代表没有懒标记
tree[lc(id)].val = tree[lc(id)].tag = tree[id].tag;
tree[rc(id)].val = tree[rc(id)].tag = tree[id].tag;
tree[lc(id)].minn = tree[lc(id)].maxn = tree[id].tag;
tree[rc(id)].minn = tree[rc(id)].maxn = tree[id].tag;
int tmid = (tl + tr) >> 1;
tree[lc(id)].sum = tree[id].tag * (tmid - tl + 1); // [tl, tmid]
tree[rc(id)].sum = tree[id].tag * (tr - tmid); // [tmid + 1, tr]
rt->tag = -1;
}
}
2. 将 区间内的每一个数全部加上一个数 。
同理,也运用懒标记。把包含在区间
类似于第一种。
仍令此节点打上的懒标记为
将左右儿子的值以及懒标记加上
另外,总和或最值会如何改变?
,因为区间内所有数都加上了 。 ,同上, 与 也是如此。
code:
inline void pushdown(int id, int tl, int tr){
if (tree[id].tag != 0) { // 不用 -1 是因为可能区间加上 -1
tree[lc(id)].val += (tree[lc(id)].tag = tree[id].tag);
tree[rc(id)].val += (tree[rc(id)].tag = tree[id].tag);
tree[lc(id)].minn += tree[id].tag; tree[lc(id)].maxn += tree[id].tag;
tree[rc(id)].minn += tree[id].tag; tree[rc(id)].maxn += tree[id].tag;
int tmid = (tl + tr) >> 1;
tree[lc(id)].sum += tree[id].tag * (tmid - tl + 1); // [tl, tmid]
tree[rc(id)].sum += tree[id].tag * (tr - tmid); // [tmid + 1, tr]
rt->tag = 0;
}
}
3. 以上两种操作都有怎么办?
这时就要考虑优先性了。
在加上一个数时,无论之前是否赋值,都不会动摇它的决心。
但在赋值时,之前的添加操作就作废了。
并且,在
附上整体代码吧(代码是求
struct Segment_Tree {
struct node { ll maxn, tag1, tag2 = -1; } tree[N << 2];
inline void pushup(int id) {
tree[id].maxn = max(tree[lc(id)].maxn, tree[rc(id)].maxn);
}
// tag1 : add && tag2 : update
inline void pushdown(int id) {
if (tree[id].tag2 != -1) {
tree[lc(id)].maxn = tree[id].tag2;
tree[rc(id)].maxn = tree[id].tag2;
tree[lc(id)].tag2 = tree[id].tag2;
tree[rc(id)].tag2 = tree[id].tag2;
tree[lc(id)].tag1 = 0;
tree[rc(id)].tag1 = 0;
tree[id].tag2 = -1;
}
if (tree[id].tag1) {
tree[lc(id)].maxn += tree[id].tag1;
tree[rc(id)].maxn += tree[id].tag1;
tree[lc(id)].tag1 += tree[id].tag1;
tree[rc(id)].tag1 += tree[id].tag1;
tree[id].tag1 = 0;
}
}
inline void build(int id, int tl, int tr) {
if (tl == tr) {
tree[id].maxn = val[tl];
return ;
}
int tmid = (tl + tr) >> 1;
build(lc(id), tl, tmid);
build(rc(id), tmid + 1, tr);
pushup(id);
}
inline void add(int id, int tl, int tr, int l, int r, int d) {
if (tl > r || tr < l) return ;
if (l <= tl && tr <= r) {
tree[id].maxn += d;
tree[id].tag1 += d;
return ;
}
int tmid = (tl + tr) >> 1; pushdown(id);
add(lc(id), tl, tmid, l, r, d);
add(rc(id), tmid + 1, tr, l, r, d);
pushup(id);
}
inline void update(int id, int tl, int tr, int l, int d) {
if (tl > l || tr < l) return ;
if (tl == tr) {
tree[id].tag1 = 0;
tree[id].maxn = d;
tree[id].tag2 = d;
return ;
}
int tmid = (tl + tr) >> 1; pushdown(id);
update(lc(id), tl, tmid, l, d);
update(rc(id), tmid + 1, tr, l, d);
pushup(id);
}
inline void update(int id, int tl, int tr, int l, int r, int d) {
if (tl > r || tr < l) return ;
if (l <= tl && tr <= r) {
tree[id].tag1 = 0;
tree[id].maxn = d;
tree[id].tag2 = d;
return ;
}
int tmid = (tl + tr) >> 1; pushdown(id);
update(lc(id), tl, tmid, l, r, d);
update(rc(id), tmid + 1, tr, l, r, d);
pushup(id);
}
inline int query(int id, int tl, int tr, int l, int r) {
if (tl > r || tr < l ) return -inf;
if (l <= tl && tr <= r) return tree[id].maxn;
int tmid = (tl + tr) >> 1; pushdown(id);
int lmax = query(lc(id), tl, tmid, l, r);
int rmax = query(rc(id), tmid + 1, tr, l, r);
return max(lmax, rmax);
}
} S;
练手题(
二.最大连续子序列 ( )
这里有一种类似于
对于一个节点
: 此区间的总和。 : 此区间从左边开头的最大连续子序列。 : 此区间从右边开头的最大连续子序列。 : 此区间的最大连续子序列。
想想怎么从两个儿子更改父亲节点的值。
如下图。
备注:① 代表
1. 的 。
很简单,左右儿子
code :
rt->sum = rt->ch[0]->sum + rt->ch[1]->sum + rt->val;
2. 的 。
首先,合并左右儿子的区间后,
其次,
两者取最大即可。
code :
rt->lmx = max(rt->ch[0]->lmx, rt->ch[0]->sum + rt->ch[1]->lmx + rt->val);
3. 的 。
与
首先,
其次,
同样取较大者。
code :
rt->rmx = max(rt->ch[1]->rmx, rt->ch[1]->sum + rt->ch[0]->rmx + rt->val);
4. 的
最后,
左儿子的 。 右儿子的 。 左儿子的 、此节点的值与右儿子的 之和,即 ② + ③。
code :
rt->mx = max({rt->ch[0]->mx, rt->ch[1]->mx, rt->ch[0]->rmx + rt->ch[1]->lmx + rt->val});
最后代码如下:
Code :
inline void pushup(node* &rt) {
if (rt == NIL) return ;
rt->siz = rt->ch[0]->siz + rt->ch[1]->siz + 1;
rt->sum = rt->ch[0]->sum + rt->ch[1]->sum + rt->val;
rt->lmx = max(rt->ch[0]->lmx, rt->ch[0]->sum + rt->ch[1]->lmx + rt->val);
rt->rmx = max(rt->ch[1]->rmx, rt->ch[1]->sum + rt->ch[0]->rmx + rt->val);
rt->mx = max({rt->ch[0]->mx, rt->ch[1]->mx, rt->ch[0]->rmx + rt->ch[1]->lmx + rt->val});
}
三.最小绝对值 ( )
即求出所有值中最接近的两个值的绝对值。
如,在序列
在二叉搜索树上,左子树的最大值小于等于当前节点值,而右子树的最小值一定大于当前节点值。
由此,一个简单的方法发芽了。
观察下图:
对于节点
也就是说,每个节点的最小绝对值有以下几种来源:
- 左子树的最小绝对值;
- 右子树的最小绝对值;
- 当前节点值减去左子树的最大值;
- 右子树的最小值减去当前节点值。
这样就能成功维护了。
Code :
inline void pushup(node* &rt) {
if (rt == NIL) return ;
rt->siz = rt->ch[0]->siz + rt->ch[1]->siz + 1;
rt->minn = min({rt->ch[0]->minn, rt->ch[1]->minn, rt->val});
rt->maxn = max({rt->ch[0]->maxn, rt->ch[1]->maxn, rt->val});
rt->mind = min(rt->ch[0]->mind, rt->ch[1]->mind);
if (rt -> ch[0] -> maxn != -inf) rt->mind = min(rt->mind, rt->val - rt->ch[0]->maxn);
if (rt -> ch[1] -> minn != inf) rt->mind = min(rt->mind, rt->ch[1]->minn - rt->val);
}
更新
有错误请指出,谢谢!