[CF997E] Good Subsegments 题解
Description
有一个 \(1-n\) 的排列 \(P\) \((1\le n\le 1.2*10^5)\)
如果区间 \([l,r]\) 中的数在排序后是连续的,那么我们称它为好区间。
例如,\([1, 3, 2, 5, 4]\)中的好区间有:
\([1,1], [1, 3], [1, 5], [2, 2], [2, 3], [2, 5], [3, 3], [4, 4], [4, 5], [5, 5].\)
有 \(q\) 次询问,每次问 \([l,r]\) 内,有多少子区间是好的?
\(n,q\le 1.2\times 10^5\)
Sol
考虑好区间的特点,假设区间 \([l,r]\) 为好区间,那么它一定满足 \(\max_{l}^r a_i - \min_{l}^r a_i = r - l\)。
考虑枚举右端点,然后考虑每个后缀 \([L,R],[L+1,R],...,[R,R]\) 的贡献,我们维护每个左端点的 \(f(x)=max - min + l - r\),观察有多少点的值为 \(0\)。
观察到 \(f(R)=0\) 且 \(0\) 为所有 \(f\) 中的最小值,那么我们要维护的就是最小值个数,那么我们可以采用线段树来维护。
我们把每个询问按照 \(r\) 排序,考虑询问移动产生的贡献:
\(l\) 变化:我们提前把每个点的权值设为 \(l\) ,这样我们就不用考虑 \(l\) 的贡献了。
\(r\) 变化:因为我们固定了 \(r\) 端点,所以每次移动时区间减 \(1\) 即可。
\(max,min\) 变化:我们用单调栈来维护每个后缀需要用到的最大与最小值,在线段树中更改即可。
由于我们需要得到每一次 \(R\) 变化的值,故线段树实现时还要记忆每次的 \(ans\)。
具体细节可以看代码。
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
int Read() {
int x = 0, f = 1; char ch = getchar();
while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
while(isdigit(ch)) {x = (x << 3) + (x << 1) + ch - '0'; ch = getchar();}
return x * f;
}
int n, a[200005], q, Ans[200005], minn[800005], mcnt[800005], ans[800005], addv[800005], tim[800005];
struct node {
int l, r, id;
bool operator < (node A) const {return r < A.r;}
}que[200005];
int stk1[200005], stk2[200005], tp1, tp2;
void pushup(int o) {
minn[o] = min(minn[o << 1], minn[o << 1 | 1]);
mcnt[o] = 0;
mcnt[o] += (minn[o << 1] == minn[o]) * mcnt[o << 1];
mcnt[o] += (minn[o << 1 | 1] == minn[o]) * mcnt[o << 1 | 1];
ans[o] = ans[o << 1] + ans[o << 1 | 1];
}
void build(int o, int l, int r) {
if(l == r) {minn[o] = l, mcnt[o] = 1; return ;}
int mid = (l + r) >> 1;
build(o << 1, l, mid); build(o << 1 | 1, mid + 1, r);
pushup(o);
}
void pusha(int o, int x) {minn[o] += x; addv[o] += x;}
void pusht(int o, int x) {ans[o] += mcnt[o] * x; tim[o] += x;}
void pushdown(int o) {
if(addv[o]) pusha(o << 1, addv[o]), pusha(o << 1 | 1, addv[o]), addv[o] = 0;
if(tim[o]) {
if(minn[o << 1] == minn[o]) pusht(o << 1, tim[o]);
if(minn[o << 1 | 1] == minn[o]) pusht(o << 1 | 1, tim[o]);
tim[o] = 0;
}
}
void modify(int o, int l, int r, int nl, int nr, int val) {
if(nl <= l && r <= nr) return pusha(o, val);
int mid = (l + r) >> 1; pushdown(o);
if(nl <= mid) modify(o << 1, l, mid, nl, nr, val);
if(mid < nr) modify(o << 1 | 1, mid + 1, r, nl, nr, val);
pushup(o);
}
int query(int o, int l, int r, int nl, int nr) {
if(nl <= l && r <= nr) return ans[o];
int mid = (l + r) >> 1, res = 0; pushdown(o);
if(nl <= mid) res += query(o << 1, l, mid, nl, nr);
if(mid < nr) res += query(o << 1 | 1, mid + 1, r, nl, nr);
return res;
}
signed main() {
n = Read();
for(int i = 1; i <= n; i++) a[i] = Read();
build(1, 1, n);
q = Read();
for(int i = 1; i <= q; i++) que[i].l = Read(), que[i].r = Read(), que[i].id = i;
sort(que + 1, que + q + 1);
int cnt = 1;
for(int i = 1; i <= n; i++) {
pusha(1, -1);
while(tp1 && a[i] > a[stk1[tp1]]) {
modify(1, 1, n, stk1[tp1 - 1] + 1, stk1[tp1], a[i] - a[stk1[tp1]]);
--tp1;
}
while(tp2 && a[i] < a[stk2[tp2]]) {
modify(1, 1, n, stk2[tp2 - 1] + 1, stk2[tp2], a[stk2[tp2]] - a[i]);
--tp2;
}
stk1[++tp1] = i; stk2[++tp2] = i;
pusht(1, 1);
while(que[cnt].r == i && cnt <= q) Ans[que[cnt].id] = query(1, 1, n, que[cnt].l ,que[cnt].r), ++cnt;
}
for(int i = 1; i <= q; i++) printf("%lld\n", Ans[i]);
return 0;
}