なんでバカのブログを読みたいの!为什么要看菜鸟的博客!|

园龄:粉丝:关注:

吉司机线段树 笔记

本文原在 2025-02-10 11:05 发布于本人洛谷博客。

P6242 【模板】线段树 3(区间最值操作、区间历史最值)

对一个数组,实现以下操作:

  1. 区间加上一个数。
  2. 区间内所有数对一个数取 \(\min\)
  3. 区间求和。
  4. 区间求最大值。
  5. 区间求历史版本最大值。

大体思路

操作 \(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 中国大陆许可协议进行许可。

posted @   Garbage_fish  阅读(11)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起