单调栈(紫书中叫滑动窗口)

利用某种单调性将o(n2)的过程优化到o(n)的优秀数据结构
区区区间间间
题解
看标程那个看不懂,只好看这个

反正就是求一个区间最值的贡献,以后见到用这个板子就好了…

很固定很套路很裸的区间最值…

标程给了我们这个提示,化简式把负号放进后项里去,相当于

求数列an最大值的区间贡献+数列bn最大值的区间贡献,其中bi=-ai

然后怎么求[1,n][1,n-1][1,2]…[2,n][2,n-1][2,3]…[n-1,n]这些区间的最大值呢。

单调栈。

借鉴的答案:

用ll[i]控制a[i]左端点最远可以到哪里,rr[i]控制a[i]右端点最远可以到哪里,

说明,当前a[i]是区间[ll[i],rr[i]]中的最大值,

而这个区间的子区间中,包含a[i]的有rr[i]-ll[i]+(rr[i]-i)*(i-ll[i])个,

①区间以i为端点,相当于在[ll[i],rr[i]]这rr[i]-ll[i]+1个点中,

选择一个非i的点当另一个端点,有rr[i]-ll[i]种

②区间不以i为端点,i被包含在区间中,

相当于在[ll[i],i-1]中选一个左端点,(i-ll[i])种,

相当于在[i+1,rr[i]]中选一个右端点,(rr[i]-i)种。

num[i]=rr[i]-ll[i]+(rr[i]-i)*(i-ll[i])

因此,每个点i共在num[i]个区间内是最大值,sum+=a[i]*num[i]。

这里我们规定,值出现相同时,越左越小,以避免重复。

因此,向左扩张时规定大于等于可扩张,向右扩张时规定大于可扩张。

在区间每踢出一个值,向左的情况–,向右的情况++。

当不能踢出来值的时候,

①若空,则最左或最右;

②非空,则说明栈顶那个值比a[i]大,该值是端界外第一个点,

所以向左的话,就是该点pos+1;向右的话,就是该点pos-1。

心得
单调栈和单调队列以其O(n)复杂度很受欢迎,

去除冗杂状态是核心,要多练掌握该类思想。

保留最大值时,可以令a[0]=a[n+1]=INF,从而越界不了,去掉size的讨论,更简洁

保留最小值时,两端-INF,同理

代码

#include<bits/stdc++.h>
using namespace std;
#define maxn 100005
#define LL long long
LL ll[maxn],rr[maxn],a[maxn];
LL work(LL n){
    memset(ll,0,sizeof(ll));
    memset(rr,0,sizeof(rr));
   stack<int>s,t;
   //可以令a[0]=-INF,a[n+1]=INF 从而去掉size的讨论 更简洁
   for(LL j=1;j<=n;j++){
       while(s.size()&&a[j]>=a[s.top()]){ // 这个地方要注意
          s.pop();
       }
       if(!s.size()) ll[j]=1;
       else ll[j]=s.top()+1;
       s.push(j);
   }
   for(LL j=n;j>=1;j--){
       while(t.size()&&a[j]>a[t.top()]){  // 一个等号一个大于号   比如 666 的情况  
          t.pop();
       }
       if(!t.size()) rr[j]=n;
       else rr[j]=t.top()-1;
       t.push(j);
   }
   LL ans=0;
   for(LL j=1;j<=n;j++){
       ans+=1LL*a[j]*1LL*(rr[j]-ll[j]+(rr[j]-j)*(j-ll[j]));
   }
   return ans;
}
int main(){
   LL t;
   cin>>t;
   while(t--){
      LL n;
      cin>>n;
      for(LL j=1;j<=n;j++){
         scanf("%d",&a[j]);
      }
      LL ans=work(n);
      //cout<<ans<<endl;
      for(LL j=1;j<=n;j++){  // 赋值的时候相当于找最大 得到的结果就是负数 正好减去 
         a[j]=-a[j];
      }
      ans+=work(n);
      cout<<ans<<endl;
   }
}

原文链接:https://blog.csdn.net/Code92007/article/details/83689045

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值