Imbalanced Array(单调栈,思维)
题目来源:https://www.luogu.com.cn/problem/CF817D
//
题意:一个数组区间[l,r]的不平衡值是区间中的极值之差,问该数组所有子段的不平衡值之和是多少?
例如[1,4,1]的所有字段:[1]=0 [1,4]=3 [1,4,1]=3 [4]=0 [4,1]=3 [1]=0,所有不平衡值之和为9。
//
思路:“最开始难到我的就是如何寻找到所有的字段,对于我来说就是暴力枚举啊,但是肯定会TLE”。这个题还是有思维的,它将最后的不平衡值转化为:每一数字的贡献值,每个数字都有两种贡献值,一种是正贡献,充当某一区间的最大值,一种是负贡献,充当某一区间的最小值。因为子段定义的是连续的区间,所以一个数字,求它左右充当了多长的最值即可。那不就是寻找左右区间第一个大于(小于)它的值嘛,单调栈。
//
代码细节:
1:代码中输出第一个大于自身的下标,例如: [66,1,23,2] 输出是[5,3,5,5],为什么不是[0,3,0,0],也就是当一个数右边没有大于自身的值 为什么输出n+1而不是0。
原因:找右边第一个大于自身的值,就相当于求出该数字 在右边区间充当了多长的最大值,最后求该数贡献值公式: ans+=a[i](i-maxl[i])(maxr[i]-i);公式中的a[i]*(maxr[i]-i)就是最为往右区间的正贡献值,(maxr[i]-i)不就是充当的多长的最值嘛,例如数组中的66,往右是当了4个最值(自身也算),那就是(5-1),如果输出0,那不就是(0-1)了。而左边输出0,就是对的。
实现的时候,最开始在单调栈里面push一个很大的值,q[top]维护大值下标n+1即可。“这个题,我可是看了,2h,才真正理解到单调栈,好吧,算法的思想才是最重要。”
2:单调栈寻找左右第一个有关的最值,与for循环遍历的顺序无关,重要的是栈中维护的是什么。
//
题解:
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+9;
vector<int>a(N),q(N),maxl(N),maxr(N),minl(N),minr(N);
signed main()
{
int n,top=0,ans=0;
cin>>n;
for(int i=1;i<=n;i++){cin>>a[i];}
//找到右边第一个大于a[i]的数字的下标: 没有=n+1
top=1;
q[top]=n+1,a[n+1]=9999999;
for(int i=n;i>=1;i--){
while(a[i]>a[q[top]]){
top--;
}
maxr[i]=q[top];
q[++top]=i;
}
//找到左边第一个大于a[i]的数字的下标 没有=0
top=0;
for(int i=n;i>=1;i--){
while(a[i]>a[q[top]] && top>0){
maxl[q[top]]=i;
top--;
}
q[++top]=i;
}
for(int i=1;i<=n;i++){
ans+=a[i]*(i-maxl[i])*(maxr[i]-i);
}
//找到右边第一个小于a[i]的数字的下标 没有=n+1
top=1;
q[top]=n+1,a[n+1]=-99999999;
for(int i=n;i>=1;i--){
while(a[i]<a[q[top]]){
top--;
}
minr[i]=q[top];
q[++top]=i;
}
//找到左边第一个小于a[i]的数字的下标 没有=0
top=0;
for(int i=n;i>=1;i--){
while(top>0 && a[i]<a[q[top]]){
minl[q[top]]=i;
top--;
}
q[++top]=i;
}
for(int i=1;i<=n;i++){
ans-=a[i]*(i-minl[i])*(minr[i]-i);
}
cout<<ans<<endl;
return 0;
}
浙公网安备 33010602011771号