牛客练习赛69 E.子串 (树状数组, 思维)

题目:传送门

题意

给出一个长度为 n 的排列 ai

规定一个区间 [l,r] 是 fair 的,当且仅当区间最小值等于 l,最大值等于 r;

求 fair 区间的个数。

1 <= n <= 1e6

思路

对于每个 i,若 ai <= i,则表示 i 这个点可以作为某些 fair 区间的右端点,那么就找 l[i],表示 i 的左边第一个满足 a[idx] > i 的 idx + 1;

对于每个 i,若 ai >= i,则表示 i 这个点可以作为某些 fair 区间的左端点,那么就找 r[i],表示 i 的右边第一个满足 a[idx] < i 的 idx - 1;

那么对于 i 这个点来说,它能作为 fair 区间的左端点的合法区间就是 [i, r[i]]; 能作为 fair 区间的右端点的合法区间就是 [l[i], i];

那我们可以先把能作为 fair 区间的左端点的合法区间存起来;

假设 i 这个点能作为 fair 区间的左端点,且合法区间为 [l, r],那我们可以存  { l, i, 1 } 和 { r, i, -1 } 这两个三元组,按照第一元素从小到大排序

然后我们可以枚举 i:1~n,计算 i 这个点作为 fair 区间的右端点时,满足条件的左端点的个数,累加起来就是答案了。

#include <bits/stdc++.h>
#define LL long long
#define rep(i, j, k) for(int i = j; i <= k; i++)
#define dep(i, j, k) for(int i = k; i >= j; i--)
#define pb push_back
#define make make_pair
#define lb(x) (x & (-x))
using namespace std;

const int N = 1e6 + 5;

int n, a[N], pos[N], l[N], r[N], t[N], cnt;

struct note {

    int val, id, x;

}tmp[N * 2];

bool cmp(note a, note b) {

    return a.val < b.val;

}

void add(int pos, int x) {

    for(int i = pos; i <= n; i += lb(i)) t[i] += x;

}

int sum(int pos) {

    int res = 0;

    for(int i = pos; i ; i -= lb(i)) {

        res += t[i];

    }

    return res;

}

void solve() {

    scanf("%d", &n);

    rep(i, 1, n) scanf("%d", &a[i]), pos[a[i]] = i;

    /// l[i] 左边第一个比 i 大的 a[idx] 的上一位,即 l[i] = idx + 1

    rep(i, 1, n) {

        l[i] = i;

        if(a[i] <= i) while(l[i] > 1 && a[l[i] - 1] <= i) l[i] = l[l[i] - 1]; /// 因为要以 i 为右端点

    }

    /// r[i] 右边第一个比 i 小的 a[idx] 的后一位,即 r[i] = idx - 1;

    dep(i, 1, n) {

        r[i] = i;

        if(a[i] >= i) while(r[i] < n && a[r[i] + 1] >= i) r[i] = r[r[i] + 1];

    }

    rep(i, 1, n) { /// 把能作为 fair 区间左端点的合法区间存起来

        if(pos[i] >= i && l[i] <= pos[i] && pos[i] <= r[i]) {

            tmp[++cnt] = {pos[i], i, 1};

            tmp[++cnt] = {r[i] + 1, i, -1};

        }

    }

    sort(tmp + 1, tmp + 1 + cnt, cmp); /// 按第一元素从小到大排序;

    int now = 1;

    LL ans = 0LL;

    rep(i, 1, n) {

        while(now <= cnt && tmp[now].val <= i) { /// tmp[now].val > i,则就不能作为 以i这个点为右端点的fair区间的左端点了 

            add(tmp[now].id, tmp[now].x);

            now++;

        }

        if(l[i] <= pos[i] && pos[i] <= i) { /// 能作为 fair 区间的右端点;
            
            /// 那就算满足的左端点,若左端点 > pos[i] 那 pos[i] 没被选上,当然不行,故就算区间 [l[i], pos[i]] 的左端点的个数即为满足条件的左端点的个数
            
            ans += sum(pos[i]) - sum(l[i] - 1);

        }

    }

    printf("%lld\n", ans);

}

int main() {

//    int _; scanf("%d", &_);
//    while(_--) solve();

    solve();

    return 0;

}

 

posted on 2020-09-13 15:07  Willems  阅读(231)  评论(0编辑  收藏  举报

导航