1. 程式人生 > 實用技巧 >【題解】[Codeforces 1359D] Yet Another Yet Another Task【單調棧 ST 表】

【題解】[Codeforces 1359D] Yet Another Yet Another Task【單調棧 ST 表】

題目連結

題意

給定一序列,求其一個子段,滿足該子段的和減去其最大值的結果儘可能大。\(n\leq 10^5\)

題解

考慮每個數作為最大值時的最大答案。每個數作為最大值時區間左右端點的範圍可以單調棧預處理。於是每次便要查詢一段內的最小字首和和一段內的最大字首和。ST 表可做(感覺也可以在單調棧的時候一起處理?)。

程式碼:

#include<bits/stdc++.h>
using namespace std;
int getint(){
	int ans=0,f=1;
	char c=getchar();
	while(c<'0'||c>'9'){
		if(c=='-')f=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9'){
		ans=ans*10+c-'0';
		c=getchar();
	}
	return ans*f;
}
const int N=1e5+10,L=18;
int a[N],l[N],r[N],s[N],n;
int smin[L][N],smax[L][N],l2[N];
void initst(){
    for(int i=0;i<=n;i++)smin[0][i]=smax[0][i]=s[i];
    for(int i=1;i<L;i++){
        for(int j=0;j<=n-(1<<i)+1;j++){
            smin[i][j]=min(smin[i-1][j],smin[i-1][j+(1<<i-1)]);
            smax[i][j]=max(smax[i-1][j],smax[i-1][j+(1<<i-1)]);
        }
    }
}
int qmin(int l,int r){
    ++r;
    int t=l2[r-l];
    return min(smin[t][l],smin[t][r-(1<<t)]);
}
int qmax(int l,int r){
    ++r;
    int t=l2[r-l];
    return max(smax[t][l],smax[t][r-(1<<t)]);
}
int main(){
    n=getint();
    for(int i=1;i<=n;i++)a[i]=getint(),s[i]=s[i-1]+a[i];
    for(int i=2;i<=n;i++)l2[i]=l2[i>>1]+1;
    initst();

    a[0]=a[n+1]=0x7f7f7f7f;
    stack<int>stk;
    stk.push(0);
    for(int i=1;i<=n;i++){
        while(a[stk.top()]<=a[i])stk.pop();
        l[i]=stk.top();
        stk.push(i);
    }
    while(stk.size())stk.pop();
    stk.push(n+1);
    for(int i=n;i>=1;--i){
        while(a[stk.top()]<=a[i])stk.pop();
        r[i]=stk.top()-1;
        stk.push(i);
    }
    
    int ans=0;
    for(int i=1;i<=n;i++){
        int s1=qmin(l[i],i-1),s2=qmax(i,r[i]);
        ans=max(ans,s[i-1]-s1 + s2-s[i]);
    }
    
    cout<<ans;
}