1. 程式人生 > 實用技巧 >[CF997E] Good Subsegments 題解

[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;
}