关于pushup与pushdown的几种常见情况

适用于线段树、平衡树等树形结构

注意:
本文中的:

  • \(lc(i) = (i \ll 1)\),即 \(i \times 2\)
  • \(rc(i) = (i \ll 1 \mid 1)\),即 \(i \times 2 + 1\)

一.区间修改,区间求和(求最值)(\(pushdown\))

分两种情况。

1. 将 \([l,r]\) 区间内的每一个数全部赋值为一个数 \(d\)

此时采用打懒标记(\(lazy\hspace{0.4em}tag\))的方法去解决。把包含在区间 \([l,r]\) 内的节点打上懒标记(即赋值为 \(d\)),每当要访问它的左右儿子时就下传。

想想怎么下传。

令此节点打上的懒标记为 \(x\)

将左右儿子的值以及懒标记改为 \(x\),再将其懒标记清空。

\(why\ ?\) 很简单。因为整个区间都要赋值为 \(d\),所以左右儿子的子孙也得更改,因此也要打上懒标记。

另外,总和或最值会如何改变?

  • \(sum = (r - l + 1) * d\),因为区间内所有数都变成了 \(d\)
  • \(maxn = minn = d\),同上。

代码如下:

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. 将 \([l,r]\) 区间内的每一个数全部加上一个数 \(d\)

同理,也运用懒标记。把包含在区间 \([l,r]\) 内的节点打上懒标记,即加上 \(d\)(因为可能同一区间添加多次)。

类似于第一种。

仍令此节点打上的懒标记为 \(x\)

将左右儿子的值以及懒标记加上 \(x\),再将其懒标记清空。

另外,总和或最值会如何改变?

  • \(sum += (r - l + 1) * d\),因为区间内所有数都加上了 \(d\)
  • \(maxn += d; minn += d\),同上,\(minn\)\(maxn\) 也是如此。

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. 以上两种操作都有怎么办?

这时就要考虑优先性了。

在加上一个数时,无论之前是否赋值,都不会动摇它的决心。

但在赋值时,之前的添加操作就作废了。

并且,在 \(pushdown\) 中先作赋值操作,再执行添加操作即可。

附上整体代码吧(代码是求 \(maxn\)\(sum\) 可以照葫芦画瓢):

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;

练手题\(luogu \hspace{0.1em}P3372\)

二.最大连续子序列 (\(pushup\))

这里有一种类似于 \(DP\) 的方法。

对于一个节点 \(Rt\),我们需要维护四个值。

  • \(sum\) : 此区间的总和。
  • \(lmx\) : 此区间从左边开头的最大连续子序列。
  • \(rmx\) : 此区间从右边开头的最大连续子序列。
  • \(mx\) : 此区间的最大连续子序列。

想想怎么从两个儿子更改父亲节点的值。

如下图。

image

备注:① 代表 \(Rt\) 左儿子的 \(lmx\),② 代表 \(Rt\) 左儿子的 \(rmx\),⑤ 代表 \(Rt\) 左儿子的 \(sum\)。③、④、⑥ 同理。

1. \(Rt\)\(sum\)

很简单,左右儿子 \(sum\) 的和加上此节点的值即可。

code :
rt->sum = rt->ch[0]->sum + rt->ch[1]->sum + rt->val;
2. \(Rt\)\(lmx\)

首先,合并左右儿子的区间后,\(Rt\)\(lmx\) 可以为左儿子的 \(lmx\),即 ① 这一段。
其次,\(Rt\)\(lmx\) 也可以由左儿子的总和、此节点的值与右儿子的 \(lmx\) 的和,即 ⑤ 与 ③ 这两段的和。
两者取最大即可。

code :
rt->lmx = max(rt->ch[0]->lmx, rt->ch[0]->sum + rt->ch[1]->lmx + rt->val);
3. \(Rt\)\(rmx\)

\(lmx\) 类似的。
首先,\(Rt\)\(rmx\) 可以为左儿子的 \(rmx\),即 ④ 这一段。
其次,\(Rt\)\(rmx\) 也可以由右儿子的总和、此节点的值与左儿子的 \(rmx\) 的和,即 ⑥ 与 ② 这两段的和。
同样取较大者。

code :
rt->rmx = max(rt->ch[1]->rmx, rt->ch[1]->sum + rt->ch[0]->rmx + rt->val);
4. \(Rt\)\(mx\)

最后,\(Rt\)\(mx\) 有三种取值。

  • \(Rt\) 左儿子的 \(mx\)
  • \(Rt\) 右儿子的 \(mx\)
  • \(Rt\) 左儿子的 \(rmx\)、此节点的值与右儿子的 \(lmx\) 之和,即 ② + ③。
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});
}

三.最小绝对值 (\(pushup\))

即求出所有值中最接近的两个值的绝对值。

如,在序列 \({1,5,9,11,20,25}\) 中,最小绝对值为 \(11-9=2\)

在二叉搜索树上,左子树的最大值小于等于当前节点值,而右子树的最小值一定大于当前节点值。

由此,一个简单的方法发芽了。

观察下图:

image

对于节点 \(11\),它左子树的最大值为 \(9\),右子树的最小值为 \(25\),那么,它的最小绝对值为 \(11-9=2\)

也就是说,每个节点的最小绝对值有以下几种来源:

  • 左子树的最小绝对值;
  • 右子树的最小绝对值;
  • 当前节点值减去左子树的最大值;
  • 右子树的最小值减去当前节点值。

这样就能成功维护了。

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);
}

更新 \(ing...\)

有错误请指出,谢谢!

posted @ 2025-01-26 14:08  云岚天上飘  阅读(86)  评论(0)    收藏  举报