2019杭电多校十 1011 Make Rounddog Happy(rmq + 分治)

题意

有一个大小为 (n ,(n leq 3e5)) 的序列,序列中的每一个数 (a_i) 满足(1 leq a_i leq n)

现定义 good subarray:对于一段区间(a_l, a_{l+1}, dots, a_r),满足区间内无重复元素并且 (max{a_l, a_{l+1}, dots, a_r} - (r-l+1) leq k)

求序列a中的 good subarray的数量。

思路

rmq + 分治的套路题。

针对最大值,可通过rmq预处理,待需要时可通过(O(log))的复杂度查询区间最大值的位置。

对于每个位置,可通过dp (瞎搞),求出与其无重复元素的左右两端。

接下来就可以通过对最大值分治。

分治过程中对于区间 ([l, r]) 可通过rmq查询 快速得到最大值的位置 (p) ,在(p-l、 r-p) 两段中选择一个较小的暴力枚举合法区间数量。

通过分治,每个点的期望遍历次数为(log) 次,整体代码期望复杂度为(O(nlog))

Code

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int maxn = 3e5+10;

ll ans;
int a[maxn], st[maxn][30];
int vis[maxn], _l[maxn], _r[maxn];
int T, n, k;

int query(int l, int r) {
    int len = r-l+1, d=0;
    while((1<<d+1)<=len) ++d; int p=1<<d;
    if(a[st[l][d]]>a[st[r-p+1][d]]) return st[l][d];
    return st[r-p+1][d];
}

void solve(int l, int r) {
    if(l > r) return;
    int p = query(l, r);
    solve(l, p-1); solve(p+1,r);
    int len = a[p]-k;
    if(p-l < r-p) {
        for (int i=p; i>=l; --i) {
            int L = max(p, i+len-1);
            int R = min(r, _r[i]);
            if(R>=L) ans += R-L+1;
        }
    } else {
        for (int i=p; i<=r; ++i) {
            int L = max(_l[i], l);
            int R = min(i-len+1, p);
            if(R>=L) ans += R-L+1;
        }
    }
}

int main() {
    scanf("%d", &T);
    while(T--) {
        scanf("%d%d", &n, &k);
        for (int i=1; i<=n; ++i)
            scanf("%d", a+i), st[i][0]=i;
        for (int j=1; j<=20; ++j) {
            int p = 1<<j-1, l=1<<j;
            for (int i=1; i+l-1<=n; ++i) {
                if(a[st[i][j-1]] > a[st[i+p][j-1]]) st[i][j] = st[i][j-1];
                else st[i][j] = st[i+p][j-1];
            }
        }

        for (int i=1; i<=n; ++i) vis[i]=n+1;
        for (int i=n; i>=1; --i) {
            _r[i]=vis[a[i]]-1;
            vis[a[i]]=i;
        }
        for (int i=n-1; i>=1; --i) _r[i] = min(_r[i], _r[i+1]);

        for (int i=1; i<=n; ++i) vis[i]=0;
        for (int i=1; i<=n; ++i) {
            _l[i]=vis[a[i]]+1;
            vis[a[i]]=i;
        }
        for (int i=2; i<=n; ++i) _l[i] = max(_l[i-1], _l[i]);

        ans = 0;
        solve(1, n);
        printf("%lld
", ans);
    }
    return 0;
}

/*
2
5 3
2 3 2 2 5
10 4
1 5 4 3 6 2 10 8 4 5
*/

总结

刚开始忽略掉了区间中无重复值这个条件,满脑子都是单调栈+瞎搞,等看到这个条件时已经神志不清了,再做题时,需要读好题目中的要求,头脑清醒的分析抽象题目、理清思路与其时间空间以及手敲的复杂度之后再上机,避免既浪费时间,又影响心态。

原文地址:https://www.cnblogs.com/acerkoo/p/11397407.html