单调栈:
定义:栈里面保持一种单调性
举例:
- 栈里面的元素为1,2,5,6(6是栈顶)
- 插入一个元素7
- 7大于栈顶元素6,可以直接入栈,此时栈为1,2,5,6,7
- 插入一个元素4
- 4小于栈顶元素7,7出栈,栈为1,2,5,6
- 4小于栈顶元素6,6出栈,栈为1,2,5
- 4小于栈顶元素5,5出栈,栈为1,2
- 4大于栈顶元素2,可以直接入栈,此时栈为1,2,4
遇到比栈顶元素大的就直接入栈,遇到比栈顶元素小的就一直出栈到栈顶元素比它小。(在递增单调性的情况下)
Max answer(南昌网络赛)
题意:给你一个序列,让你找出一个最大和(和=子串(连续)和*子串中最小值)
思路:通过单调栈求出序列中每个数作为最小值的最大范围,例如序列3,2,5,4,7,每个数作为最小值的范围为3(0 1),2(0 5),5(3 3),4(3 5),7(5 5)。这个范围可以通过单调栈来得到。确定左边第一本比i坐标小,从左到右遍历:如果栈里面有比它大的,就出栈;如果遇到比它小的或者没有了,就停下,让该位置的入栈,此时遍历到的i位置左边第一个比它小的位置为栈顶元素的位置,如果栈顶没有元素就为0。找右边最小的也同理。
让每个位置都当一次最小值,然后乘上最小值的最大范围里面的合。(对于最小值是正数管用)
(注意)负数处理,需要用到前缀和。最小值是负数的话,最大范围*最小值不一定是最大值,比如说-5 -1 5 9(-5为选取的最小值),但是最大范围*最小值=-40,实际上(最大范围*最小值)的最大值应该是(-5-1)*(-5)=30。可以确定这个最大对应的区间一定是包括最小值且在最大范围之内。要在最大范围里面找到一个包括最小值的最小和,用枚举最小值两边不同点的组合范围,如果点比较多可能就超时了。
这里给出一个前缀和的解决办法。在最大范围里面找到一个包括最小值的最小和=(最小值,最右)中找的最小前缀合-[最左,最小值)中找的最大前缀合,只需要扫描一次。
代码:
#include <algorithm>
#include <iostream>
#include <string>
#include <vector>
#include <stack>
#include <cstdlib>
#include <map>
#include <queue>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
#include <set>
using namespace::std;
const long long int INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1000007;
const int N = 5e5 + 5;
const int M = 1e3 + 5;
int a[N], stk[N], l[N], r[N];//stk单调栈数组
long long int sum[N];
int main()
{
int n;
scanf("%d",&n);
for(int i=1; i<=n; i++)
{
scanf("%d",&a[i]);
sum[i] = sum[i-1] + a[i];//求前缀和
}
sum[n+1] = sum[n];
int tp = 0;//用来控制单调栈
for(int i=1; i<=n; i++)
{
while(tp > 0 && a[stk[tp-1]] >= a[i])//如果栈里面有比它大的,就出栈
tp--;
stk[tp++] = i;//让该位置的入栈
if(tp == 1)
l[i] = 0;
else
l[i] = stk[tp-2];
}
tp = 0;
for(int i=n; i>0; i--)
{
while(tp > 0 && a[stk[tp-1]] >= a[i])
tp--;
stk[tp++] = i;
if(tp == 1)
r[i] = n+1;
else
r[i] = stk[tp-2];
}
long long int ans = -INF;
for(int i=1; i<=n; i++)
{
long long int tmp, minr = INF, maxl = -INF;//前缀和
if(a[i] < 0)
{
//找左边最小前缀合
for(int j=i; j<r[i]; j++)
{
minr = min(minr, sum[j]);
}
//找右边最大前缀和
for(int j=i-1; j>=l[i]; j--)
{
maxl = max(maxl, sum[j]);
}
tmp = (minr - maxl) * a[i];
}
else
tmp = (sum[r[i]-1] - sum[l[i]]) * a[i];
if(tmp > ans)
{
ans = tmp;
}
}
printf("%lld", ans);
return 0;
}
总结:1 学习了单调栈,虽然去年暑假做过类似的 2 学习了处理负数,这道题比较大的坑