算法学习笔记(14):区间最值操作和历史最值问题
区间最值操作, 历史最值问题
来源
吉老师2016集训队论文, oiwiki, 网络上各种博客。
概述
区间最值操作指的是:
将所有的
历史最值问题指的是:
新定义一个数组
再对
询问
区间最值操作
就考虑一个简单的问题。
1 l r
表示询问
1 l r k
表示将所有
考虑小于等于
对于线段树的一个节点, 维护
-
若
,不影响答案, return。 -
若
, 会影响最大值, 所以用上述信息更改答案, 当节点 被操作时,只修改其最大值。 -
若
, 会影响更多的值, 要递归下去找到所有的满足条件2的节点, 更改完以后回溯上来。 如果修改了严格次大值和最大值,则将这两个合并,然后递归两个儿子,看是否需要修改, 合并后对当前节点需要从儿子合并上来新的严格次大值和最大值。
1.如果有一个儿子内含有最大值和严格次大值,则递归这个儿子进行合并。
2.否则我们修改两个儿子的最大值,这两个儿子的严格次大值不需要改动,并用来更新父亲的严格次大值
修改完儿子后,合并上来新的严格次大值和最大值
递归完以后所有节点的严格次大值都是正确的, 考虑操作二时只更新了一层儿子最大值, 所以我们需要 pushdown 一下, 实际上我不需要多定义一个标记, 因为标记始终与已经修改后的最大值相等, 所以只需要判断左右儿子哪个是最大值即可, 然后将较大的
也就是如下代码
void pd(int p) {
int w = max(t[ls].mx, t[rs].mx);
if(t[ls].mx == w) t[ls].mx = t[p].mx;
if(t[rs].mx == w) t[rs].mx = t[p].mx;
}
}
实际上这是正确代码, 不同于 OI-WIKI, 我们不需要打 tag, 考虑当儿子的 t[ls].mx = t[p].mx
, 所以是正确的。
这很好理解吧。。。实在不懂手动模拟一下。
考虑, 每次递归会把线段树节点的最大值和严格次大值合并, 而线段树节点大小和为
历史最值问题
考虑一个简单的问题。
1 l r k
表示将所有
2 l r
表示询问所有
考虑对于线段树每个节点维护一个
那么考虑一下pushdown。将
其他就是基本操作了。
P6242 【模板】线段树 3(区间最值操作、区间历史最值)
考虑就是把区间最值操作和历史最值问题结合起来, 区间最值操作就是前面的直接搬过来, 但是历史最值的代码要改一下。 因为有了区间最值操作, 就会有一个对最大值进行加减的操作, 这与区间加不同。 所以要把最大值的加标记和非最大值加标记分开来。 也就是维护
考虑怎么理解这四个标记, 前面的历史最值问题的一个标记是因为区间加覆盖了整个节点的区间。 而现在有可能只对最大值进行区间加, 所以要把原来的
但是又考虑
代码
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 5e5 + 10;
const int INF = 1e18;
int n, m, a[N];
struct JI_ST{
struct Node{
int l, r, sum, mx, _mx, se, cnt, add1, _add1, add2, _add2;
}t[N << 2];
#define ls (p << 1)
#define rs (p << 1 | 1)
#define mid (t[p].l + t[p].r >> 1)
void update(int p, int d1, int _d1, int d2, int _d2) {
t[p].sum += d1 * t[p].cnt + d2 * (t[p].r - t[p].l + 1 - t[p].cnt);
t[p]._mx = max(t[p]._mx, t[p].mx + _d1);
t[p]._add1 = max(t[p]._add1, t[p].add1 + _d1);
t[p].add1 += d1;
t[p]._add2 = max(t[p]._add2, t[p].add2 + _d2);
t[p].add2 += d2;
t[p].mx += d1;
if (t[p].se != -INF) t[p].se += d2;
}
void pushdown(int p) {
int MAX = max(t[ls].mx, t[rs].mx);
if (t[ls].mx == MAX) update(ls, t[p].add1, t[p]._add1, t[p].add2, t[p]._add2);
else update(ls, t[p].add2, t[p]._add2, t[p].add2, t[p]._add2);
if (t[rs].mx == MAX) update(rs, t[p].add1, t[p]._add1, t[p].add2, t[p]._add2);
else update(rs, t[p].add2, t[p]._add2, t[p].add2, t[p]._add2);
t[p].add1 = t[p]._add1 = t[p].add2 = t[p]._add2 = 0;
}
void pushup(int p) {
t[p].sum = t[ls].sum + t[rs].sum;
t[p]._mx = max(t[ls]._mx, t[rs]._mx);
if (t[ls].mx == t[rs].mx) {
t[p].mx = t[ls].mx;
t[p].se = max(t[ls].se, t[rs].se);
t[p].cnt = t[ls].cnt + t[rs].cnt;
}
else if (t[ls].mx > t[rs].mx) {
t[p].mx = t[ls].mx;
t[p].se = max(t[ls].se, t[rs].mx);
t[p].cnt = t[ls].cnt;
}
else {
t[p].mx = t[rs].mx;
t[p].se = max(t[rs].se, t[ls].mx);
t[p].cnt = t[rs].cnt;
}
}
void build(int p, int l, int r) {
t[p].l = l, t[p].r = r;
if (l == r) return t[p] = {l, r, a[l], a[l], a[l], -INF, 1, 0, 0, 0, 0}, void();
build(ls, l, mid); build(rs, mid + 1, r);
pushup(p);
}
void add_modify(int p, int x, int y, int z) {
if (x <= t[p].l && t[p].r <= y) return update(p, z, z, z, z);
pushdown(p);
if (x <= mid) add_modify(ls, x, y, z);
if (y > mid) add_modify(rs, x, y, z);
pushup(p);
}
void min_modify(int p, int x, int y, int z) {
if (t[p].mx <= z) return;
if (x <= t[p].l && t[p].r <= y && z >= t[p].se) return update(p, z - t[p].mx, z - t[p].mx, 0, 0);
pushdown(p);
if (x <= mid) min_modify(ls, x, y, z);
if (y > mid) min_modify(rs, x, y, z);
pushup(p);
}
int Sum(int p, int x, int y) {
if (x <= t[p].l && t[p].r <= y) return t[p].sum;
pushdown(p); int res = 0;
if (x <= mid) res += Sum(ls, x, y);
if (y > mid) res += Sum(rs, x, y);
return res;
}
int Max(int p, int x, int y, int op) {
if (x <= t[p].l && t[p].r <= y) return (op == 0) ? t[p].mx : t[p]._mx;
pushdown(p); int res = -INF;
if (x <= mid) res = max(res, Max(ls, x, y, op));
if (y > mid) res = max(res, Max(rs, x, y, op));
return res;
}
}T;
signed main() {
scanf("%lld%lld", &n, &m);
for (int i = 1; i <= n; i++) {
scanf("%lld", &a[i]);
}
T.build(1, 1, n);
for (int i = 1, op, l, r, x; i <= m; i++) {
scanf("%lld%lld%lld", &op, &l, &r);
switch(op) {
case 1: scanf("%lld", &x); T.add_modify(1, l, r, x); break;
case 2: scanf("%lld", &x); T.min_modify(1, l, r, x); break;
case 3: printf("%lld\n", T.Sum(1, l, r)); break;
case 4: printf("%lld\n", T.Max(1, l, r, 0)); break;
case 5: printf("%lld\n", T.Max(1, l, r, 1)); break;
}
}
return 0;
}
P4314 CPU 监控
带区间覆盖的历史最大值。 我们考虑从上一次下传标记到现在, 我们在这个点累积了一个操作队列, 考虑这样一个问题三种情况:
- 考虑只有加法标记。
- 考虑只有覆盖标记。
- 加法标记中掺杂了覆盖标记。
1, 2 都十分简单, 多维护历史最大加标记和历史最大覆盖标记即可。 考虑第 3 种情况, 我们可以把覆盖之后的加法标记都看作覆盖标记, 所以照样按照1, 2的方法维护即可, 只是要先下传加法标记在下穿覆盖标记。 加法标记下传只是为了更新历史最大值。
点击查看代码
#include<bits/stdc++.h>
#define ls (p << 1)
#define rs (p << 1 | 1)
#define mid (t[p].l + t[p].r >> 1)
using namespace std;
const int N = 1e5 + 10;
const int INF = 1e9;
int n, q, a[N];
struct Node{
int l, r, mx, hmx, ad, had, cv, hcv;
bool vis;
}t[N << 2];
void pu(int p) {
t[p].mx = max(t[ls].mx, t[rs].mx);
t[p].hmx = max(t[ls].hmx, t[rs].hmx);
}
void upd_ad(int p, int _ad, int _had) {
if(t[p].vis) {
t[p].hcv = max(t[p].hcv, t[p].cv + _had);
t[p].cv += _ad;
t[p].hmx = max(t[p].hmx, t[p].mx + _had);
t[p].mx += _ad;
} else {
t[p].had = max(t[p].had, t[p].ad + _had);
t[p].ad += _ad;
t[p].hmx = max(t[p].hmx, t[p].mx + _had);
t[p].mx += _ad;
}
}
void upd_cv(int p, int _cv, int _hcv) {
if(t[p].vis)
t[p].hcv = max(t[p].hcv, _hcv);
else
t[p].vis = 1, t[p].hcv = _hcv;
t[p].mx = t[p].cv = _cv;
t[p].hmx = max(t[p].hmx, _hcv);
}
void pd(int p) {
//不能写 if(t[p].ad) 因为可能t[p].ad为0了, 但是t[p].had有值
upd_ad(ls, t[p].ad, t[p].had);
upd_ad(rs, t[p].ad, t[p].had);
t[p].ad = t[p].had = 0;
//不能写 if(t[p].vis) 可能是赋值为 0
if(t[p].vis) {
upd_cv(ls, t[p].cv, t[p].hcv);
upd_cv(rs, t[p].cv, t[p].hcv);
t[p].cv = t[p].hcv = t[p].vis = 0;
// vis 也是从上次下传到现在有没有被cv过, 所以要清零。
}
}
void mdf(int p ,int x, int y, int z, int op) {
if(y < t[p].l || t[p].r < x) return;
if(x <= t[p].l && t[p].r <= y) return op == 1 ? upd_ad(p, z, z) : upd_cv(p, z, z);
pd(p); mdf(ls, x, y, z, op); mdf(rs, x, y, z, op); pu(p);
}
int qry_mx(int p, int x, int y) {
if(y < t[p].l || t[p].r < x) return -INF;
if(x <= t[p].l && t[p].r <= y) return t[p].mx;
return pd(p), max(qry_mx(ls, x, y), qry_mx(rs, x, y));
}
int qry_hmx(int p, int x, int y) {
// printf("%d %d\n", t[p].l, t[p].r);
if(y < t[p].l || t[p].r < x) return -INF;
if(x <= t[p].l && t[p].r <= y) return t[p].hmx;
return pd(p), max(qry_hmx(ls, x, y), qry_hmx(rs, x, y));
}
void bld(int p, int l, int r) {
t[p].l = l, t[p].r = r;
if(l == r) return t[p] = {l, r, a[l], a[l], 0, 0, 0, 0, 0}, void();
bld(ls, l, mid); bld(rs, mid + 1, r); pu(p);
}
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; i++)
scanf("%d", &a[i]);
bld(1, 1, n);
char op[5];
scanf("%d", &q);
for(int i = 1, l, r, x; i <= q; i++) {
scanf("%s%d%d", op, &l, &r);
// puts("fuck");
switch(*op) {
case 'Q': printf("%d\n", qry_mx(1, l, r)); break;
case 'A': printf("%d\n", qry_hmx(1, l, r)); break;
case 'P': scanf("%d", &x); mdf(1, l, r, x, 1); break;
case 'C': scanf("%d", &x); mdf(1, l, r, x, 0); break;
}
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App