前言
在 前一篇博客 中,我们详细介绍了区间取最值操作。接下来我们开始介绍区间历史最值问题!
区间历史最值
对于一个序列 A A A,它的历史最值指的是它从初始化到当前达到的最大值 / 最小值,我们称之为历史最大值和历史最小值。维护历史最大值和历史最小值是有一些套路的。
没有区间最值操作时
我们可以采用延用懒标记的做法。
我们要注意到,在线段树上打标记,标记是会有生命周期的。在标记下放之前,我们永远不会访问到子节点。因此,我们可以记录在这个点打的标记的当前值和历史最值,下传标记的时候对子节点的历史最值进行更新。
如果不理解的话也没有关系,我们可以结合例题进行理解!
[BZOJ 3064] CPU监控
给出长度为 n ( n ≤ 1 0 5 ) n(n\le 10^5) n(n≤105) 的序列 A A A,定义一个辅助数组 B B B,初始时与 A A A 完全相同。给出 m ( m ≤ 1 0 5 ) m(m\le 10^5) m(m≤105) 次操作,每次操作为以下四种操作之一:
- 给出 l , r , k l,r,k l,r,k,对于所有 i ∈ [ l , r ] i\in[l,r] i∈[l,r],将 A i A_i Ai 加上 k k k
- 给出 l , r , k l,r,k l,r,k,对于所有 i ∈ [ l , r ] i\in[l,r] i∈[l,r],将 A i A_i Ai 变成 k k k
- 给出 l , r l,r l,r,求序列 A i A_i Ai 的最大值
- 给出 l , r l,r l,r,求序列 B i B_i Bi 的最大值
每次操作后,对于所有 i i i,将 B i B_i Bi 变成 max ( A i , B i ) \max(A_i,B_i) max(Ai,Bi)。
序列 B B B 就是序列 A A A 的历史最大值,询问 4 4 4 就是询问 [ l , r ] [l,r] [l,r] 中 A A A 的历史最大值的最大值。
我们先考虑线段树上的每个节点要维护一些什么信息。先考虑只有区间加操作。那么我们肯定需要维护的是区间当前最大值 m x mx mx,区间历史最大值 m x ′ mx' mx′,还有这个节点当前加了多少值 a d d add add,以及这个节点在标记下放前加的值的历史最大值 a d d ′ add' add′。那么标记下传时,我们考虑更新儿子节点的值和标记:
m x c h ′ = max ( m x c h ′ , m x c h + a d d r t ′ ) a d d c h ′ = max ( a d d c h ′ , a d d c h + a d d r t ) mx'_{ch}=\max(mx'_{ch},mx_{ch}+add'_{rt})\\ add'_{ch}=\max(add'_{ch},add_{ch}+add_{rt}) mxch′=max(mxch′,mxch+addrt′)addch′=max(addch′,addch+addrt)
其实理解了打标记和标记下传的详细过程的话,这个还是很好理解的。
接下来考虑有覆盖操作。我们显然还要维护一个 c o v , c o v ′ cov,cov' cov,cov′ 分别表示当前覆盖的值和历史最大覆盖值。我们把标记看成二元组 ( a d d , c o v ) (add,cov) (add,cov),表示先加上 a d d add add 再把值变成 c o v cov cov。那么,考虑加法操作,当 c o v cov cov 不存在值时,我们直接在 a d d add add 上进行加减,否则我们在 c o v cov cov 上进行加减。根据常识直观理解,这个做法显然是正确的。
下面给出这题的代码。
我的代码真是优美呀
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define lson l, m, rt << 1
#define rson m + 1, r, rt << 1 | 1
#define lc rt << 1
#define rc rt << 1 | 1
const int N = 1e5 + 5;
const LL inf = 1e18;
struct node{
LL add, add_;
LL cov, cov_;
LL mx, mx_;
}d[N * 4];
void pushup(int rt){
d[rt].mx = max(d[lc].mx, d[rc].mx);
d[rt].mx_ = max(d[lc].mx_, d[rc].mx_);
}
void build(int l, int r, int rt){
d[rt].cov = d[rt].cov_ = -inf;
if(l == r){
int x;
cin >> x;
d[rt].mx = d[rt].mx_ = x;
return;
}
int m = l + r >> 1;
build(lson);
build(rson);
pushup(rt);
}
void pushadd(int rt, LL k, LL k_){
d[rt].mx_ = max(d[rt].mx_, d[rt].mx + k_); d[rt].mx += k;
if(d[rt].cov != -inf) d[rt].cov_ = max(d[rt].cov_, d[rt].cov + k_), d[rt].cov += k;
else d[rt].add_ = max(d[rt].add_, d[rt].add + k_), d[rt].add += k;
}
void pushcov(int rt, LL k, LL k_){
d[rt].mx_ = max(d[rt].mx_, k_); d[rt].mx = k;
d[rt].cov_ = max(d[rt].cov_, k_); d[rt].cov = k;
}
void pushdown(int rt){
pushadd(lc, d[rt].add, d[rt].add_);
pushadd(rc, d[rt].add, d[rt].add_);
d[rt].add = d[rt].add_ = 0;
if(d[rt].cov != -inf) pushcov(lc, d[rt].cov, d[rt].cov_), pushcov(rc, d[rt].cov, d[rt].cov_), d[rt].cov = d[rt].cov_ = -inf;
}
void add(int l, int r, int rt, int a, int b, LL c){
if(l >= a && r <= b){
pushadd(rt, c, c);
return;
}
pushdown(rt);
int m = l + r >> 1;
if(a <= m) add(lson, a, b, c);
if(b > m) add(rson, a, b, c);
pushup(rt);
}
void cov(int l, int r, int rt, int a, int b, LL c){
if(l >= a && r <= b){
pushcov(rt, c, c);
return;
}
pushdown(rt);
int m = l + r >> 1;
if(a <= m) cov(lson, a, b, c);
if(b > m) cov(rson, a, b, c);
pushup(rt);
}
LL query1(int l, int r, int rt, int a, int b){
if(l >= a && r <= b) return d[rt].mx;
int m = l + r >> 1;
pushdown(rt);
LL ans = -1e18;
if(a <= m) ans = max(ans, query1(lson, a, b));
if(b > m) ans = max(ans, query1(rson, a, b));
return ans;
}
LL query2(int l, int r, int rt, int a, int b){
if(l >= a && r <= b) return d[rt].mx_;
int m = l + r >> 1;
pushdown(rt);
LL ans = -1e18;
if(a <= m) ans = max(ans, query2(lson, a, b));
if(b > m) ans = max(ans, query2(rson, a, b));
return ans;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int n;
cin >> n;
build(1, n, 1);
int m;
cin >> m;
for(int i = 1; i <= m; i++){
char o[3];
int x, y, z;
cin >> o >> x >> y;
if(o[0] == 'Q') cout << query1(1, n, 1, x, y) << '\n';
if(o[0]