思路来源
http://www.cnblogs.com/DyLoder/p/9895645.html
题解
看标程那个看不懂,只好看这个
反正就是求一个区间最值的贡献,以后见到用这个板子就好了...
很固定很套路很裸的区间最值...
标程给了我们这个提示,化简式把负号放进后项里去,相当于
求数列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;
}
}