关于单调队列/栈的总结
其实之前看过蓝书上的单调队列/栈的解释,但是还是感觉懵懵懂懂得,今天趁着做这个题目把单调队列和栈写个小总结。
首先单调队列和单调栈都是为了实现在决策队列中及时排除一定不是最优解的情况,以达到节省时间,提高效率的目的。不管是单调栈还是单调队列,其核心思想都是单调二字,也就是说,我们需要维护的永远都是队头or栈头的那个最小or最大的元素,对于单调队列而言,基本的思路就是:
- 维护队首,如果队首超出范围那就要删除
- 维护单调去除冗杂,像本题中,遇到队伍中比当前值还大,并且排名还靠前的元素我们就可以直接删除,因为对于这个问题而言,当前的元素更有生存价值!
同样的对于单调栈,也是一个类似的思路:
- 单调栈内的元素肯定是严格递增or递减的
- 如果当前元素需要进栈,对于本题而言,我们就要把栈中大于它的元素全部
push
直到找到第一个不大于他的元素或栈空为止。
给出题目:
131.最大子矩阵
题解:
我们首先考虑,如果当前矩阵的排布是一个严格的单调递增排列,那么这个问题其实很好解决了,只需要从头开始依次遍历扫描所有的高度,并每次以当前高度为基准计算面积然后求最大值即可。但是如果出现这样的情况:假设当前的高度为
h
[
i
]
h[i]
h[i],对于
h
[
i
+
1
]
h[i + 1]
h[i+1]有
h
[
i
+
1
]
>
h
[
i
]
h[i + 1] > h[i]
h[i+1]>h[i],如果以
h
[
i
+
1
]
h[i + 1]
h[i+1]的高度为基准,那么最后会发现其他高度的矩形有一部分的高度是无效的!而且在此之后出现的矩形不管高度是否大于它,因为这个小矩形的存在,之后出现的矩形是无论如何连不起来的。
这个题目就是一个典型的单调栈问题,我们可以从头扫描所有的矩阵,如果发现当前的矩阵比栈顶的高度大,那就直接让它进栈,否则就一直出栈,直到栈顶元素小于等于它或者栈空,我们就可以把一个整体的大矩形放进栈中。然后依次继续扫描,这个算法的时间复杂度为
O
(
N
)
O(N)
O(N)
AC代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 1e5 + 10;
ll a[maxn] = {},s[maxn] = {},w[maxn] = {};
int main()
{
int n;
ll ans;
while(~scanf("%d",&n)){
if(!n) break;
ans = 0;
memset(a,0,sizeof(a));
memset(s,0,sizeof(s));
memset(w,0,sizeof(w));
for(int i = 1;i <= n;i++) scanf("%lld",&a[i]);
int p = 0;a[n + 1] = 0; //在数组的最后放一个0,这样可以解决全部单调问题以及实现末尾更新答案
for(int i = 1;i <= n + 1;i++){
if(a[i] > s[p]){ //如果递增
s[++p] = a[i];
w[p] = 1; //记录每一个矩形的宽度
}
else{
ll wid = 0;
while(s[p] > a[i]){
wid += w[p]; //累计宽度
ans = max(ans,wid * s[p]); //这一步成功把所有的情况都计算了,可以画图看一下
p --; //出栈
}
s[++p] = a[i]; //把这个整体的大矩形放进去
w[p] = wid + 1;
}
}
printf("%lld\n",ans);
}
return 0;
}
135.最大子序和
题解:
这个题目确实想了好久最后感觉学到了
我们一般处理区间和问题,都是使用前缀和表示,就是
s
u
m
[
L
,
R
]
=
s
u
m
[
R
]
−
s
u
m
[
L
−
1
]
sum[L,R] = sum[R] - sum[L - 1]
sum[L,R]=sum[R]−sum[L−1]
这里一样需要用到前缀和,我们先处理右端点,如果右端点确定了,那么左端点的位置就有一个明确的范围了:
j
ϵ
[
i
−
m
,
i
−
1
]
j \epsilon [i - m,i - 1]
jϵ[i−m,i−1]。注意这里序列长度应该是m
为什么取值可以到i - m
?别急往下看
我们希望最后sum
的值最大,那就应该尽可能让
s
u
m
[
L
−
1
]
sum[L - 1]
sum[L−1]的值尽可能小,所以单调队列维护的就是一个值最小的左端点,所以前面为什么是i - m
呢,就是因为我们取得是前缀和!左端点的取值应该是要减一的。
这个题目就相当于我们对于每一个确定的右端点,我们都要维护一个左端点满足
j
ϵ
[
i
−
m
,
i
−
1
]
j \epsilon [i - m,i - 1]
jϵ[i−m,i−1]的前缀和最小。
AC代码:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<queue>
#include<map>
#include<vector>
#include<bitset>
#include<cstring>
#include<algorithm>
#include<list>
#define ll long long
#define INF 0x3f3f3f3f
#define inf -1e12
using namespace std;
const int maxn = 3e5 + 10;
ll a[maxn],sum[maxn];
list<int > q;
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;i++){
scanf("%lld",&a[i]);
sum[i] = sum[i - 1] + a[i];
}
ll maxx = sum[1];
q.push_front(0); //因为区间长度可能是1,所以从0开始算
for(int i = 1;i <= n;i++){
while(!q.empty() && i - q.back() > m) q.pop_back(); //如果超出范围那就弹出
maxx = max(maxx,sum[i] - sum[q.back()]);
// cout << "maxx = " << maxx << endl;
while(!q.empty() && sum[i] < sum[q.front()]) q.pop_front(); //去除冗杂项,比当前值大的肯定贡献不如当前值
q.push_front(i);
}
printf("%lld\n",maxx);
return 0;
}