1. 程式人生 > 其它 >牛客多校2021(五)K.King of Range(ST表、雙指標)

牛客多校2021(五)K.King of Range(ST表、雙指標)

  • 題目:King of Range

  • 題意:給出一個序列,問其存在多少個子序列(子序列肯定連續~)滿足子序列中最大值與最小值的差大於k。

  • 思路:ST表預處理 + 雙指標維護區間。

  • 解析:以下用\(maxv, minv\)表示該區間最大值和最小值,假設當遍歷到區間[l, r - 1]時,\(maxv - minv \leq k\)​說明該區間內不可能存在子區間滿足條件,但當遍歷到[l, r]區間時, \(maxv - minv > k\)​​ 說明[l, r]區間滿足條件, 並且[l, r + 1],[l, r + 2]...[l, n]一定都滿足條件。

    那麼我們可以首先用ST表預處理出所有區間的\(maxv, minv\)

    ,然後利用雙指標\(i\)為頭指標,\(j\)為尾指標,如果當前區間\(maxv - minv \leq k\),那麼尾指標\(j\)​​需要往後移動(有一些細節附加條件可以看程式碼),否則需要累加答案\(n - j + 1\)並且頭指標\(i\)需要往後移動。

    細節:此題在雙指標維護區間過程中需要多次查詢區間最大值和最小值,若log2()函式是直接用函式庫的則會T掉,log2()函式時間複雜度為log(n),所以需要預處理\(log_2(1)\)​​~\(log_2(n)\)​​

  • 程式碼:

    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #include<algorithm>
    using namespace std;
    typedef long long ll;
    const int N = 1e5 + 5;
    const int M = 20;
    
    int a[N], f[N][M], g[N][M]; //f(i, j)表示[i, i + 2^j - 1]最小值, g(i, j)表示最大值
    int lg[N]; //log2(x)
    int n, m, k;
    
    void fun() //預處理log2()函式
    {
        lg[0] = -1;
        for(int i = 1; i <= n; i++) lg[i] = lg[i >> 1] + 1;
    }
    
    void st_init()
    {
        for(int i = 1; i <= n; i++)
        {
            f[i][0] = a[i];
            g[i][0] = a[i];
        }
        for(int j = 1; j <= log2(n); j++)
        {
            for(int i = 1; i <= n + 1 - (1 << j); i++)
            {
                f[i][j] = min(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
                g[i][j] = max(g[i][j - 1], g[i + (1 << (j - 1))][j - 1]);
            }
        }
    }
    
    int query_min(int l, int r)
    {
        int len = r - l + 1;
        int k = lg[len];
        return min(f[l][k], f[r - (1 << k) + 1][k]);
    }
    
    int query_max(int l, int r)
    {
        int len = r - l + 1;
        int k = lg[len];
        return max(g[l][k], g[r - (1 << k) + 1][k]);
    }
    
    int main()
    {
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
        st_init();
        fun();
        while(m --)
        {
            scanf("%d", &k);
            ll num = 0;
            for(int i = 1, j = 1; i <= n; i++)
            {
                while( (i >= j || query_max(i, j) - query_min(i, j) <= k) && (j < n) )
                    j ++;
                if(query_max(i, j) - query_min(i, j) > k) //題目條件
                    num += ll(n - j + 1);
            }
            printf("%lld\n", num);
        }
        return 0;
    }