区间最值操作与历史最值问题(二)
前言
在 前一篇博客 中,我们详细介绍了区间取最值操作。接下来我们开始介绍区间历史最值问题!
区间历史最值
对于一个序列 AAA,它的历史最值指的是它从初始化到当前达到的最大值 / 最小值,我们称之为历史最大值和历史最小值。维护历史最大值和历史最小值是有一些套路的。
没有区间最值操作时
我们可以采用延用懒标记的做法。
我们要注意到,在线段树上打标记,标记是会有生命周期的。在标记下放之前,我们永远不会访问到子节点。因此,我们可以记录在这个点打的标记的当前值和历史最值,下传标记的时候对子节点的历史最值进行更新。
如果不理解的话也没有关系,我们可以结合例题进行理解!
[BZOJ 3064] CPU监控
给出长度为 n(n≤105)n(n\le 10^5)n(n≤105) 的序列 AAA,定义一个辅助数组 BBB,初始时与 AAA 完全相同。给出 m(m≤105)m(m\le 10^5)m(m≤105) 次操作,每次操作为以下四种操作之一:
- 给出 l,r,kl,r,kl,r,k,对于所有 i∈[l,r]i\in[l,r]i∈[l,r],将 AiA_iAi 加上 kkk
- 给出 l,r,kl,r,kl,r,k,对于所有 i∈[l,r]i\in[l,r]i∈[l,r],将 AiA_iAi 变成 kkk
- 给出 l,rl,rl,r,求序列 AiA_iAi 的最大值
- 给出 l,rl,rl,r,求序列 BiB_iBi 的最大值
每次操作后,对于所有 iii,将 BiB_iBi 变成 max(Ai,Bi)\max(A_i,B_i)max(Ai,Bi)。
序列 BBB 就是序列 AAA 的历史最大值,询问 444 就是询问 [l,r][l,r][l,r] 中 AAA 的历史最大值的最大值。
我们先考虑线段树上的每个节点要维护一些什么信息。先考虑只有区间加操作。那么我们肯定需要维护的是区间当前最大值 mxmxmx,区间历史最大值 mx′mx'mx′,还有这个节点当前加了多少值 addaddadd,以及这个节点在标记下放前加的值的历史最大值 add′add'add′。那么标记下传时,我们考虑更新儿子节点的值和标记:
mxch′=max(mxch′,mxch+addrt′)addch′=max(addch′,addch+addrt)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)
其实理解了打标记和标记下传的详细过程的话,这个还是很好理解的。
接下来考虑有覆盖操作。我们显然还要维护一个 cov,cov′cov,cov'cov,cov′ 分别表示当前覆盖的值和历史最大覆盖值。我们把标记看成二元组 (add,cov)(add,cov)(add,cov),表示先加上 addaddadd 再把值变成 covcovcov。那么,考虑加法操作,当 covcovcov 不存在值时,我们直接在 addaddadd 上进行加减,否则我们在 covcovcov 上进行加减。根据常识直观理解,这个做法显然是正确的。
下面给出这题的代码。
我的代码真是优美呀
#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] == 'A') cout << query2(1, n, 1, x, y) << '\n';if(o[0] == 'P') cin >> z, add(1, n, 1, x, y, z);if(o[0] == 'C') cin >> z, cov(1, n, 1, x, y, z);}return 0;
}
历史最值的应用
[SPOJ1557 GSS2] Can you answer these queries II
给出长度为 n(n≤105)n(n\leq 10^5)n(n≤105) 的序列 {An}(Ai∈[−105,105])\{A_n\}(A_i\in[-10^5,10^5]){An}(Ai∈[−105,105]) 和 m(m≤105)m(m\le 10^5)m(m≤105) 次询问,每次询问给出 [l,r][l,r][l,r],求区间 [l,r][l,r][l,r] 中相同值只算一次的最大子段和(子段可为空)。
这种题当然考虑离线啦!令 preipre_iprei 表示 AiA_iAi 在 [1,i−1][1,i-1][1,i−1] 中最后一次出现的位置。然后将询问按右端点排序,维护一个指针不停地往右扫,扫到位置 iii 时,将 [prei+1,i][pre_i+1,i][prei+1,i] 中每个位置的和加上 AiA_iAi。那么询问就是问 [l,r][l,r][l,r] 中历史最大值的最大值。
反正很 naivenaivenaive 就是了。复杂度是 O((n+m)logn)O((n+m)logn)O((n+m)logn) 的。
下面给出 优美的 代码。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e5 + 5;
const LL inf = 1e18;
namespace sgt{#define lson l, m, rt << 1#define rson m + 1, r, rt << 1 | 1#define lc rt << 1#define rc rt << 1 | 1struct 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){d[rt].mx = d[rt].mx_ = 0;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;}
}
using namespace sgt;
int a[N], pre[N];
LL ans[N];
map<int, int> mp;
struct node{int l, r, i;bool operator < (const node & A) const{return r < A.r;}
}q[N];
int main(){ios::sync_with_stdio(false);cin.tie(0), cout.tie(0);int n;cin >> n;for(int i = 1; i <= n; i++) cin >> a[i];int m;cin >> m;for(int i = 1; i <= m; i++) cin >> q[i].l >> q[i].r, q[i].i = i;sort(q + 1, q + m + 1);int now = 1;for(int i = 1; i <= m; i++){while(now <= q[i].r) add(1, n, 1, mp[a[now]] + 1, now, a[now]), mp[a[now]] = now, now++;ans[q[i].i] = query2(1, n, 1, q[i].l, q[i].r);}for(int i = 1; i <= m; i++) cout << ans[i] << '\n';return 0;
}
加入区间最值操作
区间最值操作与历史最值询问同向
什么叫同向呢?就是我取的是区间 max\maxmax,问的也是区间 max\maxmax。
[UOJ164 清华集训2015] V
给出长度为 n(n≤5×105)n(n\le 5\times 10^5)n(n≤5×105) 的序列 {An}\{A_n\}{An} 和 m(m≤5×105)m(m\leq 5\times 10^5)m(m≤5×105) 次操作。定义辅助数组 BBB,初始时与 AAA 完全相同。每次操作为以下五种类型之一:
- 给出 l,r,kl,r,kl,r,k,对于 ∀i∈[l,r]\forall i \in[l,r]∀i∈[l,r],将 AiA_iAi 变成 Ai+kA_i+kAi+k
- 给出 l,r,kl,r,kl,r,k,对于 ∀i∈[l,r]\forall i \in[l,r]∀i∈[l,r],将 AiA_iAi 变成 max(Ai−k,0)\max(A_i-k,0)max(Ai−k,0)
- 给出 l,r,kl,r,kl,r,k,对于 ∀i∈[l,r]\forall i \in[l,r]∀i∈[l,r],将 AiA_iAi 变成 kkk
- 给出 ppp,询问 ApA_pAp
- 给出 ppp,询问 BpB_pBp
每次操作后,对于所有 iii,将 BiB_iBi 变成 max(Ai,Bi)\max(A_i,B_i)max(Ai,Bi)。
我们令标记二元组表示 (a,v)(a,v)(a,v) 表示先加上 aaa,再对 vvv 取 max\maxmax。不难发现,每次操作都可以写成先加上一个值,再对另一个值取 max\maxmax 的形式。具体地,第一种操作可以写成 (k,−inf)(k,-inf)(k,−inf),第二种操作可以写成 (−k,0)(-k,0)(−k,0),第三种操作可以写成 (−inf,k)(-inf,k)(−inf,k)。于是我们可以用线段树解决这道题。
但是,需要注意的是,打标记的时候我们需要同时传递两种标记,因为加法操作并不能直接作用在 max\maxmax 标记上,原因是 max(v,k)+a≠max(v,k+a)\max(v,k)+a\neq \max(v,k+a)max(v,k)+a=max(v,k+a)。
具体细节还是参考代码吧!然而我这题代码写得很猥琐QAQ
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define pii pair<long long, long long>
const int N = 5e5 + 5;
const LL inf = 1e17;
namespace sgt{#define lson l, m, rt << 1#define rson m + 1, r, rt << 1 | 1#define lc rt << 1#define rc rt << 1 | 1#define x first#define y secondstruct node{LL mx, mx_;pii c, c_;}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].c = d[rt].c_ = {0, 0};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 pushtag(int rt, const pii& c, const pii& c_){d[rt].mx_ = max(d[rt].mx_, max(d[rt].mx + c_.x, c_.y)), d[rt].mx = max(d[rt].mx + c.x, c.y);d[rt].c_ = {max(d[rt].c_.x, d[rt].c.x + c_.x), max(d[rt].c_.y, max(d[rt].c.y + c_.x, c_.y))}, d[rt].c = {d[rt].c.x + c.x, max(d[rt].c.y + c.x, c.y)};d[rt].mx_ = max(-inf, d[rt].mx_), d[rt].mx = max(d[rt].mx, -inf);d[rt].c = {max(d[rt].c.x, -inf), max(d[rt].c.y, -inf)};d[rt].c_ = {max(d[rt].c_.x, -inf), max(d[rt].c_.y, -inf)};}void pushdown(int rt){pushtag(lc, d[rt].c, d[rt].c_);pushtag(rc, d[rt].c, d[rt].c_);d[rt].c = d[rt].c_ = {0, 0};}void Add(int l, int r, int rt, int a, int b, const pii& c){if(l >= a && r <= b) return pushtag(rt, c, c);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);}LL Qmax(int l, int r, int rt, int a, int b){if(l >= a && r <= b) return d[rt].mx;pushdown(rt);int m = l + r >> 1;LL ans = -inf;if(a <= m) ans = max(ans, Qmax(lson, a, b));if(b > m) ans = max(ans, Qmax(rson, a, b));return ans;}LL Qmax_(int l, int r, int rt, int a, int b){if(l >= a && r <= b) return d[rt].mx_;pushdown(rt);int m = l + r >> 1;LL ans = -inf;if(a <= m) ans = max(ans, Qmax_(lson, a, b));if(b > m) ans = max(ans, Qmax_(rson, a, b));return ans;}
}
using namespace sgt;
int main(){ios::sync_with_stdio(false);cin.tie(0), cout.tie(0);int n, m;cin >> n >> m;build(1, n, 1);for(int i = 1, o, l, r, x; i <= m; i++){cin >> o >> l;if(o <= 3) cin >> r >> x;if(o == 1) Add(1, n, 1, l, r, {x, -inf});if(o == 2) Add(1, n, 1, l, r, {-x, 0});if(o == 3) Add(1, n, 1, l, r, {-inf, x});if(o == 4) cout << Qmax(1, n, 1, l, l) << '\n';if(o == 5) cout << Qmax_(1, n, 1, l, l) << '\n';}return 0;
}
区间最值操作与历史最值询问反向
什么叫反向呢?就是取的是最大值 / 最小值,问的是历史最小值 / 最大值。
[UOJ 169] 元旦老人与数列
给出长度为 n(n≤5×105)n(n\le 5\times 10^5)n(n≤5×105) 的序列 AAA,定义辅助数组 BBB,初始时与 AAA 完全相同。给出 m(m≤5×105)m(m\le 5\times 10^5)m(m≤5×105) 次操作,每次操作为以下四种类型之一:
- 给出 l,r,kl,r,kl,r,k,对于 ∀i∈[l,r]\forall i \in[l,r]∀i∈[l,r],将 AiA_iAi 变成 Ai+kA_i+kAi+k
- 给出 l,r,kl,r,kl,r,k,对于 ∀i∈[l,r]\forall i \in[l,r]∀i∈[l,r],将 AiA_iAi 变成 max(Ai,k)\max(A_i,k)max(Ai,k)
- 给出 l,rl,rl,r,求序列 AAA 在区间 [l,r][l,r][l,r] 的最小值
- 给出 l,rl,rl,r,求序列 BBB 在区间 [l,r][l,r][l,r] 的最小值
每次操作后,对于所有 iii,将 BiB_iBi 变成 max(Ai,Bi)\max(A_i,B_i)max(Ai,Bi)。
这道题由于修改和询问操作不同向,所以我们要用 SegmentTreeBeatsSegmentTree\ BeatsSegmentTree Beats 来解决这个问题。我们还是考虑数域划分,把值分为最小值和非最小值,然后 222 操作就可以变成 111 操作的加减问题了。然后像 CPU监控 那样写就可以了!
不同的是,我们需要对最小值和非最小值分开维护,具体地,我们需要维护四种标记:
- 最小值的加减标记
- 最小值的最小历史加减标记
- 非最小值的加减标记
- 非最小值的最小历史加减标记
需要注意的一些细节是下传标记的时候需要判断一下下传的应该是最小值标记还是非最小值的标记。
下面给出 优美的 代码!
#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 = 5e5 + 5, inf = 2e9;
struct node{int mn, mn_, mn2;int amn, amn_, aot, aot_;
}d[N * 4];
void pushup(int rt){d[rt].mn = min(d[lc].mn, d[rc].mn);d[rt].mn_ = min(d[lc].mn_, d[rc].mn_);if(d[lc].mn < d[rc].mn) d[rt].mn2 = min(d[lc].mn2, d[rc].mn);else if(d[lc].mn > d[rc].mn) d[rt].mn2 = min(d[lc].mn, d[rc].mn2);else d[rt].mn2 = min(d[lc].mn2, d[rc].mn2);
}
void build(int l, int r, int rt){d[rt].mn2 = inf;if(l == r){int x;cin >> x;d[rt].mn = d[rt].mn_ = x;return;}int m = l + r >> 1;build(lson);build(rson);pushup(rt);
}
void pushadd(int rt, int amn, int amn_, int aot, int aot_){d[rt].mn_ = min(d[rt].mn_, d[rt].mn + amn_);d[rt].mn += amn;if(d[rt].mn2 != inf) d[rt].mn2 += aot; d[rt].amn_ = min(d[rt].amn_, d[rt].amn + amn_);d[rt].aot_ = min(d[rt].aot_, d[rt].aot + aot_);d[rt].amn += amn, d[rt].aot += aot;
}
void pushdown(int rt){int mn = min(d[lc].mn, d[rc].mn);pushadd(lc, d[lc].mn == mn? d[rt].amn: d[rt].aot, d[lc].mn == mn? d[rt].amn_: d[rt].aot_, d[rt].aot, d[rt].aot_);pushadd(rc, d[rc].mn == mn? d[rt].amn: d[rt].aot, d[rc].mn == mn? d[rt].amn_: d[rt].aot_, d[rt].aot, d[rt].aot_);d[rt].amn = d[rt].amn_ = d[rt].aot = d[rt].aot_ = 0;
}
void Add(int l, int r, int rt, int a, int b, int c){if(l >= a && r <= b) return pushadd(rt, c, c, c, c);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 Max(int l, int r, int rt, int a, int b, int c){if(d[rt].mn >= c) return;if(l >= a && r <= b && d[rt].mn2 > c) return pushadd(rt, c - d[rt].mn, c - d[rt].mn, 0, 0);pushdown(rt);int m = l + r >> 1;if(a <= m) Max(lson, a, b, c);if(b > m) Max(rson, a, b, c);pushup(rt);
}
int Qmin(int l, int r, int rt, int a, int b){if(l >= a && r <= b) return d[rt].mn;pushdown(rt);int m = l + r >> 1, ans = inf;if(a <= m) ans = min(ans, Qmin(lson, a, b));if(b > m) ans = min(ans, Qmin(rson, a, b));return ans;
}
int Qmin_(int l, int r, int rt, int a, int b){if(l >= a && r <= b) return d[rt].mn_;pushdown(rt);int m = l + r >> 1, ans = inf;if(a <= m) ans = min(ans, Qmin_(lson, a, b));if(b > m) ans = min(ans, Qmin_(rson, a, b));return ans;
}
int main(){ios::sync_with_stdio(false);cin.tie(0), cout.tie(0);int n, m;cin >> n >> m;build(1, n, 1);for(int i = 1, o, l, r, x; i <= m; i++){cin >> o >> l >> r;if(o <= 2) cin >> x;if(o == 1) Add(1, n, 1, l, r, x);if(o == 2) Max(1, n, 1, l, r, x);if(o == 3) cout << Qmin(1, n, 1, l, r) << '\n';if(o == 4) cout << Qmin_(1, n, 1, l, r) << '\n';}return 0;
}
BOSS
[UOJ 170] Picks loves segment tree VIII
给出长度为 n(n≤5×105)n(n\le 5\times 10^5)n(n≤5×105) 的序列 AAA,定义辅助数组 B,CB, CB,C,初始时与 AAA 完全相同。给出 m(m≤5×105)m(m\le 5\times 10^5)m(m≤5×105) 次操作,每次操作为以下六种类型之一:
对于所有的 i∈[l,r]i∈[l,r]i∈[l,r],将 AiA_iAi 变成 Ai+cA_i+cAi+c。
对于所有的 i∈[l,r]i∈[l,r]i∈[l,r],将 AiA_iAi 变成 max(Ai,d)\max(A_i,d)max(Ai,d)。
对于所有的 i∈[l,r]i∈[l,r]i∈[l,r],询问 AiA_iAi 的最小值。
对于所有的 i∈[l,r]i∈[l,r]i∈[l,r],询问 BiB_iBi 的最小值。
对于所有的 i∈[l,r]i∈[l,r]i∈[l,r],将 AiA_iAi 变成 min(Ai,e)\min(A_i,e)min(Ai,e)。
对于所有的 i∈[l,r]i∈[l,r]i∈[l,r],询问 CiC_iCi 的最大值。
在每一次操作结束之后,picks 都会进行一次更新:对于所有的 i∈[1,n]i∈[1,n]i∈[1,n],将 BiB_iBi 变成 min(Bi,Ai)\min(B_i,A_i)min(Bi,Ai),Ci 变成 max(Ci,Ai)\max(C_i,A_i)max(Ci,Ai)。
这题比较复杂,同时有取最大值和最小值操作。我们还是把值分成最小值,最大值和其他值。对这三种操作分别维护三个标记:
- 当前加减标记
- 当前加减标记的历史最大值
- 当前加减标记的历史最小值
之所以要维护这么多个标记,是因为当数域重合时,我们要进行许多恶心的判断。如果读者有幸不看题解自己尝试一下这道题,就能够体会到这题在下传标记时有多恶心了!笔者调了 333 小时有余,终于写完了这份 6kb6kb6kb 的代码!
下面给出 恶心的 代码。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;void debug_out(){cerr << endl;
}
template<typename Head, typename... Tail>
void debug_out(Head H, Tail... T){cerr << " " << to_string(H);debug_out(T...);
}
#ifdef local
#define debug(...) cerr<<"["<<#__VA_ARGS__<<"]:",debug_out(__VA_ARGS__)
#else
#define debug(...) 55
#endifconst int N = 5e5 + 5, inf = 2e9 + 5;
namespace SgtBeats{#define lson l, m, rt << 1#define rson m + 1, r, rt << 1 | 1#define lc rt << 1#define rc rt << 1 | 1struct node{int l, r, mx, mx2, mx_, mn, mn2, mn_;int amx, amx_, amn, amn_, aot, aotx_, aotn_;int amx__, amn__;}d[N * 4];void pushup(int rt){d[rt].mx_ = max(d[lc].mx_, d[rc].mx_);d[rt].mn_ = min(d[lc].mn_, d[rc].mn_);if(d[lc].mx > d[rc].mx){d[rt].mx = d[lc].mx;d[rt].mx2 = max(d[lc].mx2, d[rc].mx);}else if(d[lc].mx < d[rc].mx){d[rt].mx = d[rc].mx;d[rt].mx2 = max(d[lc].mx, d[rc].mx2);}else{d[rt].mx = d[lc].mx;d[rt].mx2 = max(d[lc].mx2, d[rc].mx2);}if(d[lc].mn < d[rc].mn){d[rt].mn = d[lc].mn;d[rt].mn2 = min(d[lc].mn2, d[rc].mn);}else if(d[lc].mn > d[rc].mn){d[rt].mn = d[rc].mn;d[rt].mn2 = min(d[lc].mn, d[rc].mn2);}else{d[rt].mn = d[lc].mn;d[rt].mn2 = min(d[lc].mn2, d[rc].mn2);}}void pushadd(int rt, int f1, int f2, int amx, int amx_, int amx__, int amn, int amn_, int amn__, int aot, int aotx_, int aotn_){if(d[rt].mx == d[rt].mn){if(f1 && f2) assert(amx == amn), amx_ = amn__ = max(amx_, amn__), amn_ = amx__ = max(amn_, amx__), aot = amx, aotx_ = amx_, aotn_ = amn_;else if(f1) amn = amx, amn_ = amx__, amn__ = amx_, aot = amx, aotx_ = amx_, aotn_ = amn_;else if(f2) amx = amn, amx_ = amn__, amx__ = amn_, aot = amx, aotx_ = amx_, aotn_ = amn_;else amn = amx = aot, amn_ = aotx_, amn_ = aotn_;}if(d[rt].mx2 == d[rt].mn) d[rt].mx2 += amn;else if(d[rt].mx2 != -inf) d[rt].mx2 += aot;if(d[rt].mn2 == d[rt].mx) d[rt].mn2 += amx;else if(d[rt].mn2 != inf) d[rt].mn2 += aot;d[rt].mx_ = max(d[rt].mx_, d[rt].mx + amx_), d[rt].mn_ = min(d[rt].mn_, d[rt].mn + amn_);d[rt].amx_ = max(d[rt].amx_, d[rt].amx + amx_), d[rt].amn_ = min(d[rt].amn_, d[rt].amn + amn_);d[rt].amx__ = min(d[rt].amx__, d[rt].amx + amx__), d[rt].amn__ = max(d[rt].amn__, d[rt].amn + amn__);d[rt].aotx_ = max(d[rt].aotx_, d[rt].aot + aotx_), d[rt].aotn_ = min(d[rt].aotn_, d[rt].aot + aotn_); d[rt].mx += amx, d[rt].mn += amn, d[rt].amx += amx, d[rt].amn += amn, d[rt].aot += aot;//debug(rt, amx, amx_, d[rt].l, d[rt].r, d[rt].mx, d[rt].mx_, d[rt].mn, d[rt].mn_);}void pushdown(int rt){int mx = max(d[lc].mx, d[rc].mx), mn = min(d[lc].mn, d[rc].mn);pushadd(lc, d[lc].mx == mx, d[lc].mn == mn, d[lc].mx == mx? d[rt].amx: d[rt].aot, d[lc].mx == mx? d[rt].amx_: d[rt].aotx_, d[lc].mx == mx? d[rt].amx__: d[rt].aotn_, d[lc].mn == mn? d[rt].amn: d[rt].aot, d[lc].mn == mn? d[rt].amn_: d[rt].aotn_, d[lc].mn == mn? d[rt].amn__: d[rt].aotx_, d[rt].aot, d[rt].aotx_, d[rt].aotn_);pushadd(rc, d[rc].mx == mx, d[rc].mn == mn, d[rc].mx == mx? d[rt].amx: d[rt].aot, d[rc].mx == mx? d[rt].amx_: d[rt].aotx_, d[rc].mx == mx? d[rt].amx__: d[rt].aotn_, d[rc].mn == mn? d[rt].amn: d[rt].aot, d[rc].mn == mn? d[rt].amn_: d[rt].aotn_, d[rc].mn == mn? d[rt].amn__: d[rt].aotx_, d[rt].aot, d[rt].aotx_, d[rt].aotn_);d[rt].amx = d[rt].amx_ = d[rt].amx__ = d[rt].amn = d[rt].amn_ = d[rt].amn__ = d[rt].aot = d[rt].aotx_ = d[rt].aotn_ = 0;}void build(int l, int r, int rt){d[rt].mx2 = -inf, d[rt].mn2 = inf; d[rt].l = l, d[rt].r = r;if(l == r){int x;cin >> x;d[rt].mx = d[rt].mn = d[rt].mx_ = d[rt].mn_ = x;return;}int m = l + r >> 1;build(lson);build(rson);pushup(rt);}void Add(int l, int r, int rt, int a, int b, int c){if(l >= a && r <= b) return pushadd(rt, 1, 1, c, c, c, c, c, c, c, c, c);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 Max(int l, int r, int rt, int a, int b, LL c){if(d[rt].mn >= c) return;if(l >= a && r <= b && d[rt].mn2 > c) return pushadd(rt, 0, 1, 0, 0, 0, c - d[rt].mn, c - d[rt].mn, c - d[rt].mn, 0, 0, 0);pushdown(rt);int m = l + r >> 1;if(a <= m) Max(lson, a, b, c);if(b > m) Max(rson, a, b, c);pushup(rt);}void Min(int l, int r, int rt, int a, int b, LL c){if(d[rt].mx <= c) return;if(l >= a && r <= b && d[rt].mx2 < c) return pushadd(rt, 1, 0, c - d[rt].mx, c - d[rt].mx, c - d[rt].mx, 0, 0, 0, 0, 0, 0);pushdown(rt);int m = l + r >> 1;if(a <= m) Min(lson, a, b, c);if(b > m) Min(rson, a, b, c);pushup(rt);}LL Qmin(int l, int r, int rt, int a, int b){if(l >= a && r <= b) return d[rt].mn;pushdown(rt);int m = l + r >> 1;LL ans = inf;if(a <= m) ans = min(ans, Qmin(lson, a, b));if(b > m) ans = min(ans, Qmin(rson, a, b));return ans;}LL Qmin_(int l, int r, int rt, int a, int b){if(l >= a && r <= b) return d[rt].mn_;pushdown(rt);int m = l + r >> 1;LL ans = inf;if(a <= m) ans = min(ans, Qmin_(lson, a, b));if(b > m) ans = min(ans, Qmin_(rson, a, b));return ans;}LL Qmax_(int l, int r, int rt, int a, int b){if(l >= a && r <= b) return d[rt].mx_;pushdown(rt);int m = l + r >> 1;LL ans = -inf;if(a <= m) ans = max(ans, Qmax_(lson, a, b));if(b > m) ans = max(ans, Qmax_(rson, a, b));return ans;}
}
using namespace SgtBeats;
int main(){// freopen("data.in", "r", stdin);
// freopen("222.out", "w", stdout);ios::sync_with_stdio(false);cin.tie(0), cout.tie(0);int n, m;cin >> n >> m;build(1, n, 1);for(int i = 1, o, l, r, x; i <= m; i++){cin >> o >> l >> r;if(o <= 2 || o == 5) cin >> x;if(o == 1) Add(1, n, 1, l, r, x);if(o == 2) Max(1, n, 1, l, r, x);if(o == 3) cout << Qmin(1, n, 1, l, r) << '\n';if(o == 4) cout << Qmin_(1, n, 1, l, r) << '\n';if(o == 5) Min(1, n, 1, l, r, x);if(o == 6) cout << Qmax_(1, n, 1, l, r) << '\n'; }return 0;
}
后记
从笔者开始学习这块内容到写完这篇博客花了整整一个星期。在这一个星期中,笔者虽然感受到了调试的痛苦,但是却收获到了更多包括但不限于知识的幸福!
参考资料
- 2016 年 IOI 国家集训队论文——吉如一《区间最值操作与历史最值问题》
- 灵梦大佬的博客
区间最值操作与历史最值问题(二)相关推荐
- 浅谈区间最值操作与历史最值问题
浅谈树状数组与线段树:https://www.cnblogs.com/AKMer/p/9946944.html 区间最值问题 以Gorgeous Sequence为例: 对于线段树上每个结点,我们维护 ...
- 区间最值操作与历史最值问题(一)
前言 本文主要讲解一种叫做 SegmentTreeBeatsSegmentTree~BeatsSegmentTree Beats 的维护区间取最值操作的问题,以及维护区间历史最值的方法.本文参考自许多 ...
- 【地狱副本】数据结构之线段树Ⅲ——区间最值/赋值/修改/历史值操作(HDU5306,Tyvj 1518,【清华集训2015】V,HDU6315,HDU1828,POJ3162)
文章目录 Gorgeous Sequence Tyvj 1518 CPU监控 [清华集训2015]V Naive Operations Picture Walking Race Gorgeous Se ...
- 【bzoj4355】Play with sequence 线段树区间最值操作
题目描述 维护一个长度为N的序列a,现在有三种操作: 1)给出参数U,V,C,将a[U],a[U+1],...,a[V-1],a[V]都赋值为C. 2)给出参数U,V,C,对于区间[U,V]里的每个数 ...
- 【bzoj4695】最假女选手 线段树区间最值操作
题目描述 给定一个长度为 N 序列,编号从 1 到 N .要求支持下面几种操作: 1.给一个区间[L,R] 加上一个数x 2.把一个区间[L,R] 里小于x 的数变成x 3.把一个区间[L,R] ...
- 【uoj#164】[清华集训2015]V 线段树维护历史最值
题目描述 给你一个长度为 $n$ 的序列,支持五种操作: $1\ l\ r\ x$ :将 $[l,r]$ 内的数加上 $x$ : $2\ l\ r\ x$ :将 $[l,r]$ 内的数减去 $x$ , ...
- OpenCV学习笔记(四十一)——再看基础数据结构core OpenCV学习笔记(四十二)——Mat数据操作之普通青年、文艺青年、暴力青年 OpenCV学习笔记(四十三)——存取像素值操作汇总co
OpenCV学习笔记(四十一)--再看基础数据结构core 记得我在OpenCV学习笔记(四)--新版本的数据结构core里面讲过新版本的数据结构了,可是我再看这部分的时候,我发现我当时实在是看得太马 ...
- BZOJ #3064. Tyvj 1518 CPU监控(线段树,历史最值)
BZOJ #3064. Tyvj 1518 CPU监控(线段树,历史最值) Solution 我们考虑用线段树维护此题. 先不考虑历史最值. 大概需要维护一种特殊的懒标记(x,y)(x,y)(x,y) ...
- 【C 语言】字符串模型 ( 两头堵模型 | 将 两头堵模型 抽象成业务模块函数 | 形参返回值 | 函数返回值 | 形参指针判空 | 形参返回值操作 )
文章目录 一.将 两头堵模型 抽象成业务模块函数 二.完整代码示例 一.将 两头堵模型 抽象成业务模块函数 将 两头堵模型 抽象成业务模块函数 相关要点 : 形参返回值 : 函数的返回值 , 一般使用 ...
- 如何统计php数组值的和,php数组键值操作和数组统计函数-函数
1.数组函数//作用:提供了很多官方写的很多有用的代码段,提高编写速度 1)数组的键值操作函数 array_values();//获取数组中的值 array_keys();//获取数组中的键 in_a ...
最新文章
- [JDK翻译][Executor][ExecutorService]
- Java调用Python遇到的一系列问题与解决方案
- 一篇对伪共享、缓存行填充和CPU缓存讲的很透彻的文章
- vue3与vue2的详细区别
- Windows计算机功能Java源码
- 程序员的前20个搜索和排序算法面试问题
- 51单片机有几个通用io口_51单片机IO口的四种使用方法
- 我的博客园css样式
- WPF入门(三):简单绑定 - 绑定到页面元素
- mysql5.6安装步骤详细_详解MySQL5.6安装步骤
- 开箱-艳云脚本云控系统
- 深度学习硬件环境配置
- 前端技术周刊 2018-12-24:移动无限加载
- 牛逼!IDEA 护眼方案来了…
- 2022-12-09 Ubuntu16.4中访问另一台Ubuntu samba共享出来的目录方法
- 简单的excel考勤表
- U盘文件系统FAT32、exFAT、NTFS之间有什么区别?
- 95% 的算法都是基于这 6 种算法思想
- 买Mac做设计玩游戏?各类Mac图形设计能力浅析
- RAC 之 RMAN 备份