吉司机线段树 笔记
本文原在 2025-02-10 11:05 发布于本人洛谷博客。
P6242 【模板】线段树 3(区间最值操作、区间历史最值)
对一个数组,实现以下操作:
- 区间加上一个数。
- 区间内所有数对一个数取 \(\min\)。
- 区间求和。
- 区间求最大值。
- 区间求历史版本最大值。
大体思路
操作 \(1,3,4\) 是简单的。
操作 \(5\) 我们可以维护 tag 的历史最大值,下传时就可以用当前儿子最大值,加上 tag 的历史最大值,维护儿子的历史最大值了。
对于操作 \(2\),同时维护区间内的次大值,当递归到某个区间,使得只有最大值需要取 \(\min\) 时,取 \(\min\) 返回即可,据说这样的复杂度是 \(\log^2\) 的,因此需要对区间最大值进行特殊维护。
实现
1. 建树
定义以下线段树:
struct TREE { int s, mx, cnt, mx2, mxh; int t1, t2, tm1, tm2; }
其中:
- \(s\) 是区间和。
- \(mx\) 是区间最大值。
- \(cnt\) 是区间内最大值的数量。
- \(mx_2\) 是区间内次大值。
- \(mx_h\) 是区间历史最大值。
- \(t_1\) 是即将下传给儿子当前最大值的 tag。
- \(t_2\) 是即将下传给儿子当前其他值的 tag。
- \(tm_1\) 是即将下传给儿子当前最大值的历史最大 tag。
- \(tm_2\) 是即将下传给儿子当前其他值的历史最大 tag。
\(t_2,tm_2\) 的意义:当前区间的最大值可能只来自两个儿子区间的其中一个,所以另一个儿子区间不能使用最大值的 tag。
根据定义,push_up 和 build 是容易实现的:
void push_up(int rt) { t[rt].s = t[ls].s + t[rs].s; // 求和 t[rt].mx = max(t[ls].mx, t[rs].mx); // 取最大值 t[rt].mxh = max(t[ls].mxh, t[rs].mxh); // 取历史最大值 if (t[ls].mx > t[rs].mx) { // 最大值来自左子树 t[rt].cnt = t[ls].cnt; // 最大值个数和左子树最大值个数相同 t[rt].mx2 = max(t[ls].mx2, t[rs].mx); // 次大值为右子树最大值或左子树次大值 } else if (t[rs].mx > t[ls].mx) { // 最大值来自右子树 t[rt].cnt = t[rs].cnt; // 同理 t[rt].mx2 = max(t[rs].mx2, t[ls].mx); } else { // 左右子树均出现最大值 t[rt].cnt = t[ls].cnt + t[rs].cnt; // 最大值个数相加 t[rt].mx2 = max(t[ls].mx2, t[rs].mx2); // 次大值为左子树次大值或右子树次大值 } } void build(int rt, int l, int r) { if (l == r) { t[rt].s = t[rt].mx = t[rt].mxh = a[l]; t[rt].mx2 = -oo; t[rt].cnt = 1; return; } int mid = l + r >> 1; build(ls, l, mid); build(rs, mid + 1, r); push_up(rt); }
2. 更新
(1). 操作 \(1\)
先考虑怎么下传标记。
void modify(int rt, int len, int t1, int t2, int tm1, int tm2) { // rt 是被下传的节点,len 是被下传节点的区间长度,其他是父节点的参数 t[rt].s += t1 * t[rt].cnt + t2 * (len - t[rt].cnt); // 求和要区分最大值和其他值 t[rt].mxh = max(t[rt].mxh, t[rt].mx + tm1); // 历史最大值等于下传前的最大值加上 tag 的历史最大值 t[rt].mx += t1; // 当前最大值直接更新 if (t[rt].mx2 != -oo) // 如果有次大值才更新次大值 t[rt].mx2 += t2; // 直接更新 t[rt].tm1 = max(t[rt].tm1, t[rt].t1 + tm1); // 历史最大 tag 值更新同历史最大值 t[rt].tm2 = max(t[rt].tm2, t[rt].t2 + tm2); t[rt].t1 += t1, t[rt].t2 += t2; // tag 直接更新 } void push_down(int rt, int len) { // rt 是当前子树,len 是当前区间长度 int tmp = max(t[ls].mx, t[rs].mx); if (t[ls].mx == tmp) // 最大值来自左子树 modify(ls, len - len / 2, t[rt].t1, t[rt].t2, t[rt].tm1, t[rt].tm2); // 下传 else // 最大值不来自左子树 modify(ls, len - len / 2, t[rt].t2, t[rt].t2, t[rt].tm2, t[rt].tm2); // 左子树的最大值是当前子树的非最大值 if (t[rs].mx == tmp) // 同理 modify(rs, len / 2, t[rt].t1, t[rt].t2, t[rt].tm1, t[rt].tm2); else modify(rs, len / 2, t[rt].t2, t[rt].t2, t[rt].tm2, t[rt].tm2); t[rt].t1 = t[rt].t2 = t[rt].tm1 = t[rt].tm2 = 0; }
于是就可以直接更新了:
void update_add(int rt, int l, int r, int x, int y, int v) { if (l > y or r < x) return; if (x <= l and r <= y) { modify(rt, r - l + 1, v, v, v, v); return; } push_down(rt, r - l + 1); int mid = l + r >> 1; update_add(ls, l, mid, x, y, v); update_add(rs, mid + 1, r, x, y, v); push_up(rt); }
(2). 操作 \(2\)
根据前面提到的思路,一直递归直到只有最大值需要被修改再修改。
void update_mn(int rt, int l, int r, int x, int y, int v) { if (l > y or r < x or v >= t[rt].mx) // 没有需要修改的 return; if (x <= l and r <= y and t[rt].mx2 < v) { // 满足修改条件 int tmp = t[rt].mx - v; t[rt].s -= tmp * t[rt].cnt; // 区间总和少了 t[rt].mx = v; // 取 min t[rt].t1 -= tmp; // 对区间最大值的 tag 也应减少 return; } push_down(rt, r - l + 1); int mid = l + r >> 1; update_mn(ls, l, mid, x, y, v); update_mn(rs, mid + 1, r, x, y, v); push_up(rt); }
3. 查询
这一部分就是直接查了。
int query_s(int rt, int l, int r, int x, int y) { if (l > y or r < x) return 0; if (x <= l and r <= y) return t[rt].s; push_down(rt, r - l + 1); int mid = l + r >> 1; return query_s(ls, l, mid, x, y) + query_s(rs, mid + 1, r, x, y); } int query_mx(int rt, int l, int r, int x, int y) { if (l > y or r < x) return -oo; if (x <= l and r <= y) return t[rt].mx; push_down(rt, r - l + 1); int mid = l + r >> 1; return max(query_mx(ls, l, mid, x, y), query_mx(rs, mid + 1, r, x, y)); } int query_mxh(int rt, int l, int r, int x, int y) { if (l > y or r < x) return -oo; if (x <= l and r <= y) return t[rt].mxh; push_down(rt, r - l + 1); int mid = l + r >> 1; return max(query_mxh(ls, l, mid, x, y), query_mxh(rs, mid + 1, r, x, y)); }
本文作者:Garbage fish's Blog
本文链接:https://www.cnblogs.com/Garbage-fish/p/18710021
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步