南昌网络赛 I题 单调栈+线段树 The Preliminary Contest for ICPC China Nanchang National Invitational

博客介绍了单调栈,即元素单调递增或递减的栈。通过单调栈从1到n和从n到1扫描,可找到每个元素作为最小元素所能管辖区间的左右边界。之后用线段树维护序列前缀和,针对最小元素正负情况分别计算管辖区间子区间和。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

单调栈:栈里面的元素全部都是单调递增或单调递减的栈

思路:枚举每个元素作为最小元素所能管辖的区间,通过单调栈从1~n扫一遍找到右边界,再从n~1扫一遍找到每个元素所能管辖的区间的左边界,具体的操作就是当前元素小于栈顶元素是,栈顶元素出栈,界定其左(右)边界,重复此操作,直到当前元素大于等于栈顶元素,把当前元素入栈,找完每个元素所管辖的区间之后,我们再用线段树维护一下这个序列的前缀和,对于最小元素为负数的,我们要的是其管辖区间子区间和最小的,只需i~r找最小的前缀和,l-1~i找最大的前缀和,相减就能得到和最小的子区间,需要注意一点,若最大前缀和为负数,则令其等于0,然后对于最小元素为正数的,只要把它所管辖的区间里的数全加起来就行了

代码:

#include<bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
#define ll long long
const int maxn=5e5+9;
ll a[maxn],sum[maxn];//sum表示前缀和
struct node{
    ll val,pos;
};
struct Inter{
    ll l,r;
}inter[maxn];//inter表示第i个元素所管辖的区间
stack<node>stk;
struct tree{
    ll mx,mi;
}tr[maxn*4];
void pushup(ll rt){
    tr[rt].mx=max(tr[rt<<1].mx,tr[rt<<1|1].mx);
    tr[rt].mi=min(tr[rt<<1].mi,tr[rt<<1|1].mi);
}
void build(ll l,ll r,ll rt){
    if(l==r){
        tr[rt].mx=sum[l];
        tr[rt].mi=sum[l];
        return;
    }
    ll mid=(l+r)>>1;
    build(l,mid,rt<<1);
    build(mid+1,r,rt<<1|1);
    pushup(rt);
}
ll query_mx(ll ql,ll qr,ll l,ll r,ll rt){
    if(l>=ql&&r<=qr){
        return tr[rt].mx;
    }
    ll mid=(l+r)>>1,mx=-1e18;
    if(mid>=ql)mx=max(query_mx(ql,qr,l,mid,rt<<1),mx);
    if(mid<qr)mx=max(mx,query_mx(ql,qr,mid+1,r,rt<<1|1));
    return mx;
}
ll query_mi(ll ql,ll qr,ll l,ll r,ll rt){
    if(l>=ql&&r<=qr){
        return tr[rt].mi;
    }
    ll mid=(l+r)>>1,mi=1e18;
    if(mid>=ql)mi=min(query_mi(ql,qr,l,mid,rt<<1),mi);
    if(mid<qr)mi=min(mi,query_mi(ql,qr,mid+1,r,rt<<1|1));
    return mi;
}
int main(){
        ios::sync_with_stdio(0);
        cin.tie(0);
        ll i,j,k,n,l,r;
        cin>>n;
        for(i=1;i<=n;i++){
            cin>>a[i];
            sum[i]+=sum[i-1]+a[i];
        }
        for(i=1;i<=n;i++){
            if(stk.empty()||a[i]>=stk.top().val){
                stk.push({a[i],i});
            }
            else{
                r=stk.top().pos;
                while(!stk.empty()&&a[i]<stk.top().val){
                    node u=stk.top();
                    inter[u.pos].r=r;
                    stk.pop();
                }
                stk.push({a[i],i});
            }
        }
        if(!stk.empty())r=stk.top().pos;
        while(!stk.empty()){
            node u=stk.top();
            inter[u.pos].r=r;
            stk.pop();
        }
        for(i=n;i>=1;i--){
            if(stk.empty()||a[i]>=stk.top().val){
                stk.push({a[i],i});
            }
            else{
                l=stk.top().pos;
                while(!stk.empty()&&a[i]<stk.top().val){
                    node u=stk.top();
                    inter[u.pos].l=l;
                    stk.pop();
                }
                stk.push({a[i],i});
            }
        }
        if(!stk.empty())l=stk.top().pos;
        while(!stk.empty()){
            node u=stk.top();
            inter[u.pos].l=l;
            stk.pop();
        }
        build(0,n,1);
        ll ans=-1e18;
        for(i=1;i<=n;i++){
            if(a[i]>=0){
                ll l=inter[i].l,r=inter[i].r;
                ans=max(ans,a[i]*(sum[r]-sum[l-1]));
            }
            else{
                ll l=inter[i].l,r=inter[i].r;
                ll mi=query_mi(i,r,0,n,1);
                ll mx=query_mx(l-1,i,0,n,1);
                if(mx<0)mx=0;
                ans=max(ans,a[i]*(mi-mx));
            }
        }
        cout<<ans<<endl;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值