区间最值操作
区间最值操作,指的是对区间 [l,r][l,r][l,r] 里的全部数对 xxx 取 min\minmin 或取 max\maxmax,即 ai=min(ai,x)a_i=\min(a_i,x)ai=min(ai,x) 或 ai=max(ai,x)a_i=\max(a_i,x)ai=max(ai,x)。
一道例题
HDU5306 Gorgeous Sequence

我们用线段树维护每个区间的最大值 mxmxmx 和严格次大值 sesese,以及 mxmxmx 的个数 cntcntcnt。
考虑 111 操作,对于一个线段树上的区间,我们分类讨论一下:
- 如果 mx≤t:mx\leq t:mx≤t:
那么我们可以直接 returnreturnreturn - 如果 mx>t,se<t:mx>t,se < t:mx>t,se<t:
我们在区间打一个 tagtagtag 标记,并更新最大值为 ttt,同时更新 sum=sum−cnt∗(mx−t)sum = sum - cnt * (mx - t)sum=sum−cnt∗(mx−t) - 如果 se≥t:se\ge t:se≥t:
因为此时我们并不知道哪些数大于 ttt,于是我们暴力递归子区间
这样子操作的复杂度看起来很玄学,但其实通过势能分析,可以推出复杂度大概是 O(mlogn)O(mlogn)O(mlogn) 的,详情可参考吉老师 201620162016 年的国家集训队论文 (我实在不会啊QAQ
代码如下:
#include <bits/stdc++.h>
#define lson l, m, rt << 1
#define rson m + 1, r, rt << 1 | 1
using namespace std;
typedef long long LL;
typedef unsigned long long uLL;
LL z = 1;
int read(){
int x, f = 1;
char ch;
while(ch = getchar(), ch < '0' || ch > '9') if(ch == '-') f = -1;
x = ch - '0';
while(ch = getchar(), ch >= '0' && ch <= '9') x = x * 10 + ch - 48;
return x * f;
}
const int N = 1e6 + 5;
int v1[N * 4], cnt[N * 4], tag[N * 4], v2[N * 4]; // v1是最大值,v2是次大值,tag 是最值标记,cnt 是最小值的个数
LL sum[N * 4];
void pushup(int rt){
if(v1[rt << 1] > v1[rt << 1 | 1]){
v1[rt] = v1[rt << 1];
cnt[rt] = cnt[rt << 1];
v2[rt] = max(v2[rt << 1], v1[rt << 1 | 1]);
}
else if(v1[rt << 1] < v1[rt << 1 | 1]){
v1[rt] = v1[rt << 1 | 1];
cnt[rt] = cnt[rt << 1 | 1];
v2[rt] = max(v1[rt << 1], v2[rt << 1 | 1]);
}
else{
v1[rt] = v1[rt << 1];
cnt[rt] = cnt[rt << 1] + cnt[rt << 1 | 1];
v2[rt] = max(v2[rt << 1], v2[rt << 1 | 1]);
}
sum[rt] = sum[rt << 1] + sum[rt << 1 | 1];
}
void build(int l, int r, int rt){
tag[rt] = -1;
if(l == r){
int x = read();
v1[rt] = sum[rt] = x, v2[rt] = -1, cnt[rt] = 1;//次大值初始化为 -1
return;
}
int m = l + r >> 1;
build(lson);
build(rson);
pushup(rt);
}
void addtag(int rt, int c){
if(v1[rt] <= c) return;
sum[rt] -= z * cnt[rt] * (v1[rt] - c);//更新 sum
v1[rt] = tag[rt] = c;//更新最值
}
void pushdown(int rt){
if(tag[rt] != -1){
addtag(rt << 1, tag[rt]);//给左儿子加 tag
addtag(rt << 1 | 1, tag[rt]);//给右儿子加 tag
tag[rt] = -1;
}
}
void update(int l, int r, int rt, int a, int b, int c){
if(v1[rt] <= c) return;
if(l >= a && r <= b && v2[rt] < c){//该区间的最小值都变成 c
addtag(rt, c);
return;
}
pushdown(rt);
int m = l + r >> 1;
if(a <= m) update(lson, a, b, c);
if(b > m) update(rson, a, b, c);
pushup(rt);
}
int query_max(int l, int r, int rt, int a, int b){
if(l >= a && r <= b) return v1[rt];
int m = l + r >> 1, ans = 0;
pushdown(rt);
if(a <= m) ans = max(ans, query_max(lson, a, b));
if(b > m) ans = max(ans, query_max(rson, a, b));
return ans;
}
LL query_sum(int l, int r, int rt, int a, int b){
if(l >= a && r <= b) return sum[rt];
int m = l + r >> 1;
LL ans = 0;
pushdown(rt);
if(a <= m) ans += query_sum(lson, a, b);
if(b > m) ans += query_sum(rson, a, b);
return ans;
}
int main(){
int i, j, n, m, T, o, a, b, c;
T = read();
while(T--){
n = read(); m = read();
build(1, n, 1);
for(i = 1; i <= m; i++){
o = read(), a = read(), b = read();
if(o == 0){
c = read();
update(1, n, 1, a, b, c);
}
else if(o == 1){
printf("%d\n", query_max(1, n, 1, a, b));
}
else{
printf("%lld\n", query_sum(1, n, 1, a, b));
}
}
}
return 0;
}
CCPC2020网络赛A art class
题意
在笛卡尔坐标系下,有 nnn 次操作,每次给出 (l,r,h)(l,r,h)(l,r,h),贴着 xxx 轴在 [l,r][l,r][l,r] 上放一个高为 hhh 的矩形,问每次操作后图形的总周长。
其中,n≤2e5,l,r,h≤1e9n\leq 2e5,l,r,h\leq 1e9n≤2e5,l,r,h≤1e9。
分析
对于水平方向,就是一个简单的区间覆盖问题。
对于竖直方向,每次操作可以看作 [l,r][l,r][l,r] 的 aia_iai 对 hhh 取 max\maxmax 。我们要求的,其实是 ∑∣ai−ai+1∣\sum |{a_i-a_{i+1}}|∑∣ai−ai+1∣。
我们考虑用吉老师线段树维护这个东西,我们对区间 [l,r][l,r][l,r],要保存最小值 mnmnmn,次小值 sesese,以及 [l,r−1][l,r-1][l,r−1] 中 ai,ai+1a_i,a_{i+1}ai,ai+1 中有且仅有一个最小值的个数 cntcntcnt。
这样,每次我们加 max\maxmax 标记时,sum=sum−cnt∗(x−mn)sum=sum-cnt*(x-mn)sum=sum−cnt∗(x−mn)。
总复杂度是 O(nlogn)O(nlogn)O(nlogn)。
代码如下
#include <bits/stdc++.h>
#include <ext/pb_ds/hash_policy.hpp>
#include <ext/pb_ds/assoc_container.hpp>
#define lson l, m, rt << 1
#define rson m + 1, r, rt << 1 | 1
#define lc rt << 1
#define rc rt << 1 | 1
using namespace __gnu_pbds;
using namespace std;
typedef long long LL;
typedef unsigned long long uLL;
LL z = 1;
int ksm(int a, int b, int p){
int s = 1;
while(b){
if(b & 1) s = z * s * a % p;
a = z * a * a % p;
b >>= 1;
}
return s;
}
const int N = 4e5 + 5, inf = 1e9 + 5;
struct node{
int l, r, h;
}q[N];
int v[N], tot, tag[N * 4], cnt[N * 4], mn[N * 4], tg[N * 4], se[N * 4], L[N * 4], R[N * 4];
LL v1[N * 4], v2[N * 4];
void pushup(int rt){
v2[rt] = v2[lc] + v2[rc] + abs(L[rc] - R[lc]);
L[rt] = L[lc];//最左端
R[rt] = R[rc];//最右端
if(mn[lc] < mn[rc]){
cnt[rt] = cnt[lc];
mn[rt] = mn[lc];
se[rt] = min(se[lc], mn[rc]);
}
else if(mn[lc] > mn[rc]){
cnt[rt] = cnt[rc];
mn[rt] = mn[rc];
se[rt] = min(mn[lc], se[rc]);
}
else{
cnt[rt] = cnt[lc] + cnt[rc];
mn[rt] = mn[lc];
se[rt] = min(se[lc], se[rc]);
}//上面是固定套路
if((R[lc] == mn[rt] || L[rc] == mn[rt]) && R[lc] != L[rc]) cnt[rt]++; //看连接处
}
void pushup(int rt, int l, int r){
if(tag[rt]) v1[rt] = v[r + 1] - v[l];
else v1[rt] = v1[lc] + v1[rc];
}
void build(int l, int r, int rt){
v1[rt] = v2[rt] = L[rt] = R[rt] = tag[rt] = tg[rt] = mn[rt] = cnt[rt] = 0;
se[rt] = inf;
if(l == r) return;
int m = l + r >> 1;
build(lson);
build(rson);
}
void update1(int l, int r, int rt, int a, int b){
if(tag[rt]) return;
if(l >= a && r <= b){
tag[rt] = 1;
v1[rt] = v[r + 1] - v[l];
return;
}
int m = l + r >> 1;
if(a <= m) update1(lson, a, b);
if(b > m) update1(rson, a, b);
pushup(rt, l, r);
}
void addtag(int rt, int c){
if(mn[rt] >= c) return;
v2[rt] -= z * cnt[rt] * (c - mn[rt]);
if(L[rt] == mn[rt]) L[rt] = c; //记得更新最左端和最右端
if(R[rt] == mn[rt]) R[rt] = c;
tg[rt] = mn[rt] = c;
}
void pushdown(int rt){
if(tg[rt]){
addtag(lc, tg[rt]);
addtag(rc, tg[rt]);
tg[rt] = 0;
}
}
void update2(int l, int r, int rt, int a, int b, int c){
if(mn[rt] >= c) return;
if(l >= a && r <= b && se[rt] > c){//固定套路
addtag(rt, c);
return;
}
pushdown(rt);
int m = l + r >> 1;
if(a <= m) update2(lson, a, b, c);
if(b > m) update2(rson, a, b, c);
pushup(rt);
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int i, j, n, m, T, l, r, h;
cin >> T;
while(T--){
cin >> n;
tot = 0;
for(i = 1; i <= n; i++){
cin >> l >> r >> h;
q[i] = {l, r, h};
v[++tot] = l, v[++tot] = r;
}
sort(v + 1, v + tot + 1);
m = unique(v + 1, v + tot + 1) - v - 1;//离散化
build(0, m + 1, 1); // 这里是 [0,m + 1],是因为两端的长度也要算入
for(i = 1; i <= n; i++){
l = lower_bound(v + 1, v + m + 1, q[i].l) - v;
r = lower_bound(v + 1, v + m + 1, q[i].r) - v;
update1(0, m + 1, 1, l, r - 1);
update2(0, m + 1, 1, l, r - 1, q[i].h);
cout << v1[1] * 2 + v2[1] << "\n";
}
}
return 0;
}
总结
这种对区间取 max\maxmax 的题,一般要保存最小值 mnmnmn,严格次小值 sesese,以及和最小值相关的次数 cntcntcnt 还有一个标记 tagtagtag。每次加标记时,修改最小值 mnmnmn,同时更新要维护的值。
本文介绍了区间最值操作的概念,并通过吉老师线段树讲解了如何处理区间取min或max的问题。文章以HDU5306 Gorgeous Sequence和CCPC2020网络赛A题为例,详细分析了操作过程和复杂度,提供了相应的代码实现,总结了处理这类问题的关键点。
9096

被折叠的 条评论
为什么被折叠?



