[题解]CF1988E Range Minimum Sum

思路

如果没有删除操作,就是一个典中典。

直接枚举最小值 \(a_i\),可以轻松 \(\Theta(n)\) 找到其所能管辖到的最大区间 \([L_i,R_i]\)。形式化地说,找到一个最小的 \(L_i\) 和一个最大的 \(R_i\),使得 \(\min_{L_i \leq x \leq R_i}\{a_x\} = a_i\)

那么它对答案的贡献是 \(a_i \times (i - L_i + 1) \times (R_i - i + 1)\)

加入删除操作,也是差不多的做法,依旧是枚举最小值。可以按照删除元素的位置 \(t\) 进行如下分讨:

  1. \(t \in [1,L_i - 1)\)\(t \in (R_i + 1,n]\),显然对答案无影响,贡献为 \(a_i \times (i - L_i + 1) \times (R_i - i + 1)\)

  2. \(t \in [L_i,i)\),在 \(i\) 左边的选择会少一种,贡献为 \(a_i \times (i - L_i) \times (R_i - i + 1)\)

  3. \(t \in (i,R_i]\),在 \(i\) 右边的选择会少一种,贡献为 \(a_i \times (i - L_i + 1) \times (R_i - i)\)

  4. \(t = L_i - 1\)\(t = R_i + 1\),比较特殊的一种情况,当删除这个位置后,\(a_i\) 对应新的区间范围可能变化,需要重新求解。可以直接利用 ST 表二分求解。

所有贡献都是连续的,直接差分计算即可。

Code

#include <bits/stdc++.h>
#define re register
#define fst first
#define snd second
#define ll long long

using namespace std;

typedef pair<int,int> pii;
const int N = 5e5 + 10,M = 24,inf = 1e9 + 10;
int n;
int arr[N],L[N],R[N],lg[N];
ll c[N];

inline int read(){
    int r = 0,w = 1;
    char c = getchar();
    while (c < '0' || c > '9'){
        if (c == '-') w = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9'){
        r = (r << 3) + (r << 1) + (c ^ 48);
        c = getchar();
    }
    return r * w;
}

struct ST{
    #define pot(x) (1 << x)

    pii dp[N][M];

    inline pii Add(pii a,pii b){
        if (a.fst == b.fst) return {a.fst,min(a.snd,b.snd)};
        else if (a.fst < b.fst) return {a.fst,min(a.snd,b.fst)};
        else return {b.fst,min(a.fst,b.snd)};
    }

    inline void build(){
        for (re int i = 2;i <= n;i++) lg[i] = lg[i >> 1] + 1;
        for (re int i = 1;i <= n;i++) dp[i][0] = {arr[i],inf};
        for (re int j = 1;j <= lg[n];j++){
            for (re int i = 1;i + pot(j) - 1 <= n;i++) dp[i][j] = Add(dp[i][j - 1],dp[i + pot(j - 1)][j - 1]);
        }
    }

    inline pii query(int l,int r){
        int len = r - l + 1;
        return Add(dp[l][lg[len]],dp[r - pot(lg[len]) + 1][lg[len]]);
    }

    #undef pot
}st;

inline void add(int l,int r,ll k){
    if (l > r) return;
    c[l] += k; c[r + 1] -= k;
}

inline void solve(){
    n = read();
    fill(c + 1,c + n + 5,0);
    for (re int i = 1;i <= n;i++) arr[i] = read();
    st.build();
    for (re int i = 1;i <= n;i++) L[i] = R[i] = i;
    for (re int i = 1;i <= n;i++){
        while (L[i] > 1 && arr[L[i] - 1] > arr[i]) L[i] = L[L[i] - 1];
    }
    for (re int i = n;i;i--){
        while (R[i] < n && arr[R[i] + 1] > arr[i]) R[i] = R[R[i] + 1];
    }
    for (re int i = 1;i <= n;i++){
        int lnum = i - L[i] + 1,rnum = R[i] - i + 1;
        add(1,L[i] - 2,1ll * lnum * rnum * arr[i]);
        add(L[i],i - 1,1ll * (lnum - 1) * rnum * arr[i]);
        add(i + 1,R[i],1ll * lnum * (rnum - 1) * arr[i]);
        add(R[i] + 2,n,1ll * lnum * rnum * arr[i]);
        if (L[i] > 1){
            int l = 1,r = L[i] - 1;
            while (l < r){
                int mid = l + r >> 1;
                if (st.query(mid,i).snd == arr[i]) r = mid;
                else l = mid + 1;
            }
            int Lnum = i - l;
            add(L[i] - 1,L[i] - 1,1ll * Lnum * rnum * arr[i]);
        }
        if (R[i] < n){
            int l = R[i] + 1,r = n;
            while (l < r){
                int mid = l + r + 1 >> 1;
                if (st.query(i,mid).snd == arr[i]) l = mid;
                else r = mid - 1;
            }
            int Rnum = l - i;
            add(R[i] + 1,R[i] + 1,1ll * lnum * Rnum * arr[i]);
        }
    }
    for (re int i = 1;i <= n;i++) printf("%lld ",c[i] += c[i - 1]);
    puts("");
}

signed main(){
    int T; T = read();
    while (T--) solve();
    return 0;
}
posted @ 2024-07-24 18:34  WBIKPS  阅读(51)  评论(0)    收藏  举报