CF997EGood Subsegments (线段树+单调栈)

Posted lstete

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CF997EGood Subsegments (线段树+单调栈)相关的知识,希望对你有一定的参考价值。

Description

原题链接
给你一个长度为(n)的排列(~P),定义一段子区间是好的,当且仅当这个子区间内的值构成了连续的一段。例如对于排列({1,3,2 })([1, 1], [2, 2], [3, 3], [2, 3], [1, 3])是好的区间。
(q)次询问,每次询问(L,R), 求有多少(L leq l leq r leq R),满足([l, r])是好的区间。(1 leq n, q leq 1.2 imes 10 ^ 5).

Solution

可以发现,区间([l, r])是好的,当且仅当(~(Max_{i = l}^{r} - Min_{i = l} ^ {r}) - (r - l) = 0).
考虑维护一段区间上式的最小值(以下的最小值都指上式最小值),每一个最小值为(0)的位置都可以和当前的(r)组成一个好的区间。不难发现,一个右端点能产生的贡献为它左边的点的最小值为(0)的个数,于是可以在线段树上维护最小值和最小值个数,以及每个最小值为(0)的位置产生的贡献。
由于右边新加的点会对已有的点产生影响,考虑离线询问,按右端点排序,用两个单调栈分别维护当前(Min)(Max)。右端点右移时,势必会使整个区间的最小值减一,也势必会使其未右移前的右端点对答案产生一轮贡献,对每次处理右端点等于当前枚举点的答案。

Code

#include <bits/stdc++.h>

#define For(i, j, k) for (int i = j; i <= k; ++ i)
#define Forr(i, j, k) for (int i = j; i >= k; -- i)

using namespace std;

typedef long long ll;

inline int read() {
    int x = 0, p = 1; char c = getchar();
    for (; !isdigit(c); c = getchar()) if (c == ‘-‘) p = -1;
    for (; isdigit(c); c = getchar()) x = (x << 1) + (x << 3) + (c ^ 48);
    return x * p;
}

inline void File() {
    freopen("CF997E.in", "r", stdin);
    freopen("CF997E.out", "w", stdout);
}

const int N = 1.2e5 + 10;
int n, q, a[N], s1[N], t1, s2[N], t2, tt = 1; ll ans[N];

struct Query {
    int id, l, r;
    bool operator < (const Query &rhs) const { return r < rhs.r; }
} Q[N];

namespace Segment_Tree {
#define lc (rt << 1)
#define rc (rt << 1 | 1)
#define mid (l + r >> 1)

    const int MAXN = N << 2;
    int mn[MAXN], tag[MAXN], t[MAXN]; ll sum[MAXN], tg[MAXN];
    
    inline void pushdown(int rt) {
        if (tag[rt]) {
            mn[lc] += tag[rt], mn[rc] += tag[rt];
            tag[lc] += tag[rt], tag[rc] += tag[rt];
            tag[rt] = 0;
        }

        if (tg[rt]) {
            if (mn[lc] == mn[rt]) sum[lc] += 1ll * t[lc] * tg[rt], tg[lc] += tg[rt];
            if (mn[rc] == mn[rt]) sum[rc] += 1ll * t[rc] * tg[rt], tg[rc] += tg[rt];
            tg[rt] = 0;
        }
    }

    inline void pushup(int rt) {
        t[rt] = 0, mn[rt] = min(mn[lc], mn[rc]);
        
        t[rt] = mn[rt] == mn[lc] ? t[rt] + t[lc] : t[rt];
        t[rt] = mn[rt] == mn[rc] ? t[rt] + t[rc] : t[rt];

        sum[rt] = sum[lc] + sum[rc];
    }

    inline void Build(int rt, int l, int r) {
        mn[rt] = l, t[rt] = 1;
        if (l ^ r) Build(lc, l, mid), Build(rc, mid + 1, r);
    }

    inline void update(int rt, int l, int r, int L, int R, int v) {
        if (L <= l && r <= R) { mn[rt] += v, tag[rt] += v; return ; }
        pushdown(rt); if (L <= mid) update(lc, l, mid, L, R, v);
        if (R > mid) update(rc, mid + 1, r, L, R, v); pushup(rt);
    }   

    inline ll query(int rt, int l, int r, int L, int R) {
        if (L <= l && r <= R) return sum[rt]; pushdown(rt);
        if (R <= mid) return query(lc, l, mid, L, R);
        if (L > mid) return query(rc, mid + 1, r, L, R);
        return query(lc, l, mid, L, R) + query(rc, mid + 1, r, L, R);
    }

#undef lc
#undef rc
#undef mid
}

int main() {
    File();

    using namespace Segment_Tree;

    n = read(); For(i, 1, n) a[i] = read();
    q = read(); For(i, 1, q) Q[i].l = read(), Q[i].r = read(), Q[i].id = i;

    sort(Q + 1, Q + 1 + q);

    Build(1, 1, n);

    For(nr, 1, n) {

        mn[1] -= 1, tag[1] -= 1;

        for (; t1 && a[s1[t1]] < a[nr]; -- t1)
            update(1, 1, n, s1[t1 - 1] + 1, s1[t1], a[nr] - a[s1[t1]]);
        s1[++ t1] = nr;
        
        for (; t2 && a[s2[t2]] > a[nr]; -- t2)
            update(1, 1, n, s2[t2 - 1] + 1, s2[t2], a[s2[t2]] - a[nr]);
        s2[++ t2] = nr;

        sum[1] += t[1], tg[1] += 1;

        for (; tt <= q && Q[tt].r == nr; ++ tt)
            ans[Q[tt].id] = query(1, 1, n, Q[tt].l, nr);
    }

    For(i, 1, q) printf("%lld
", ans[i]);

    return 0;
}




以上是关于CF997EGood Subsegments (线段树+单调栈)的主要内容,如果未能解决你的问题,请参考以下文章

CF997A Convert to Ones

cf997B

CF997B Roman Digits

CF997C Sky Full of Stars

(组合数学)不定方程的解+猜测——cf997B

Codeforecs H. Subsegments