洛谷 P3246 - [HNOI2016]序列(单调栈+前缀和)
阅读原文时间:2023年07月09日阅读:1

题面传送门

这道题为什么我就没想出来呢/kk

对于每组询问 \([l,r]\),我们首先求出区间 \([l,r]\) 中最小值的位置 \(x\),这个可以用 ST 表实现 \(\mathcal O(n\log n)-\mathcal O(1)\) 维护,那么显然 \(\forall l'\in[l,x],r'\in[x,r],\min\limits_{t\in[l',r']}a_t=a_x\),产生的贡献为 \((r-x+1)(x-l+1)a_x\),于是我们只用计算 \([x+1,r],[l,x-1]\) 两个区间的贡献即可。

这个东西怎么计算呢?考虑枚举区间的右端点 \(R\),我们要计算 \([x+1,R],[x+2,R],\cdots,[R,R]\) 这 \(R-x\) 个区间的最小值之和,我们记 \(pre_x\) 为最大的满足 \(y<x,a_y\le a_x\) 的 \(y\)——\(pre_x\) 显然可以用单调栈在线性时间内求出。再记序列 \(p_1,p_2,\cdots,p_k\) 满足 \(p_0=0,p_k=R,\forall i\in[1,k],p_{i-1}=pre_{p_i}\) 的序列(说白了就是单调栈求完 \(pre_R\) 之后,栈底到栈顶位置上元素的下标依次形成的序列),那么显然 \(\forall i\in[1,k],l\in(p_{i-1},p_i]\) 都有 \(\min\limits_{t=l}^xa_t=a_{p_i}\),也就是说 \(a_{p_i}\) 位置会成为 \(p_i-p_{i-1}\) 个位置的最小值。如果我们记 \(f_R\) 为 \([1,R],[2,R],\cdots,[R,R]\) 的最小值之和,那么根据之前的推论有递推式 \(f_R=f_{pre_R}+(R-pre_R)a_R\),可线性求出。

这里还有一个问题,那就是我们要计算 \([x+1,R],[x+2,R],\cdots,[R,R]\) 的贡献 instead of \([1,R],[2,R],\cdots,[R,R]\),也就是说我们要减去 \([1,R],[2,R],\cdots,[x,R]\) 的贡献。这东西又怎么求呢?很明显 \(p\) 序列有一个性质就是必定 \(\exist id\in[1,k]\) 满足 \(p_{id}=x\),正确性显然,并且容易注意到 \(\forall l\in[1,x],\min\limits_{t=l}^Ra_t=\min\limits_{t=l}^xa_t\),也就是说 \([1,R],[2,R],\cdots,[x,R]\) 的贡献其实就是 \([1,x],[2,x],\cdots,[x,x]\),因此只需拿 \(f_R-f_x\) 就可以得到 \([x+1,R],[x+2,R],\cdots,[R,R]\) 的贡献。

故最终 \([x+1,r]\) 的贡献之和就是 \(\sum\limits_{R=x+1}^rf_R-f_x\),这个显然前缀和随便算一下就行了。求 \([l,x-1]\) 的贡献也同理。

时间复杂度 \(n\log n\),瓶颈在于 RMQ。

const int MAXN=1e5;
const int LOG_N=18;
const int INF=0x3f3f3f3f;
int n,qu,a[MAXN+5],pre[MAXN+5],nxt[MAXN+5];
ll fl[MAXN+5],fr[MAXN+5],gl[MAXN+5],gr[MAXN+5];
pii st[MAXN+5][LOG_N+2];
pii query(int l,int r){
    int k=31-__builtin_clz(r-l+1);
    return min(st[l][k],st[r-(1<<k)+1][k]);
}
int main(){
    scanf("%d%d",&n,&qu);a[0]=a[n+1]=-INF;
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    stack<int> stk;stk.push(0);
    for(int i=1;i<=n;i++){
        while(!stk.empty()&&a[stk.top()]>=a[i]) stk.pop();
        pre[i]=stk.top();stk.push(i);
    } while(!stk.empty()) stk.pop();
    stk.push(n+1);
    for(int i=n;i;i--){
        while(!stk.empty()&&a[stk.top()]>=a[i]) stk.pop();
        nxt[i]=stk.top();stk.push(i);
    }
//    for(int i=1;i<=n;i++) printf("%d %d\n",pre[i],nxt[i]);
    for(int i=1;i<=n;i++) fl[i]=fl[pre[i]]+1ll*a[i]*(i-pre[i]),gl[i]=gl[i-1]+fl[i];
    for(int i=n;i;i--) fr[i]=fr[nxt[i]]+1ll*a[i]*(nxt[i]-i),gr[i]=gr[i+1]+fr[i];
    for(int i=1;i<=n;i++) st[i][0]=mp(a[i],i);
    for(int i=1;i<=LOG_N;i++) for(int j=1;j+(1<<i)-1<=n;j++)
        st[j][i]=min(st[j][i-1],st[j+(1<<i-1)][i-1]);
    while(qu--){
        int l,r;scanf("%d%d",&l,&r);int p=query(l,r).se;
        printf("%lld\n",1ll*a[p]*(p-l+1)*(r-p+1)+gr[l]-gr[p]-1ll*fr[p]*(p-l)+gl[r]-gl[p]-1ll*fl[p]*(r-p));
    }
    return 0;
}