单调栈归纳
根据博客[单调栈原理及应用 详解 附各种类型的题目练习 - zuzhiang的博客 - CSDN博客](https://blog.csdn.net/zuzhiang/article/details/78134247)及其他博客总结。(详情看原博客,比我的详细多了)
用处:
- 最基础的应用就是给定一组数,针对每个数,寻找它和它右边第一个比它大的数之间有多少个数。
- 给定一序列,寻找某一子序列,使得子序列中的最小值乘以子序列的长度最大。
- 给定一序列,寻找某一子序列,使得子序列中的最小值乘以子序列所有元素和最大。
用处1对应题目:
POJ 3250 – Bad Hair Day
题意:有一群牛站成一排,每头牛都是面朝右的,每头牛可以看到他右边身高比他小的牛。给出每头牛的身高,要求每头牛能看到的牛的总数。
思路:这也就是应用1所说的求每个数和它右边第一个比它大的数之间的数的个数,分别求出后相加即可。朴素的做法是双重循环遍历,时间复杂度为O(n^2),用单调栈为O(n)。
坑点:结果范围,不能只有int ,而需要long long int。
#include<iostream>
#include<stack>
using namespace std;
int main()
{
int n;
cin >> n;
int q[n],sum[n];
for(int i = 0;i < n;i++)
{
cin >> q[i];
}
stack<int> s;
for(int i = 0;i < n;i++)
{
if(s.empty() || q[s.top()] > q[i])
{
s.push(i);
}
else
{
int tem = s.top();
sum[tem] = i - tem - 1;
s.pop();
i--;
}
}
while(!s.empty())
{
int tem = s.top();
sum[tem] = n - tem - 1;
s.pop();
}
long long int ans = 0;//坑点:结果范围。
for(int i = 0;i < n;i++)
{
ans += sum[i];
}
cout << ans << endl;
}
用处2对应题目:
POJ 2559 – Largest Rectangle in a Histogram
题意:有N个矩形,宽度都为1,给出N个矩形的高度,求由这N个矩形组成的图形包含的最大的矩形面积。
思路:可以转化为求区间最小值乘以区间长度的最大值。普通的思路是两重循环遍历,时间复杂度为O(n^2),用单调栈为O(n)。
解法(frompoj 2559(单调栈) - 越看越喜欢啊 - CSDN博客):
把高度看成一个序列,当高度递增的时候,答案在这个递增序列往回寻找。例如,
1,2,3,4,5,6;更新答案的时候有这么几个选择(6x1),(5x2),(4x3),(3x4),(2x5),(1x6).(高x宽)
当高度出现下降的时候,这时左端比它高的矩形有一部分是无用的。
例如,1,2,3,4,5,6,4;可以看成1,2,3,4,4,4,4;因为我们采取往左更新的方式,5,6这两个大于4的部分是无法被使用上的(往右更新也一样)。所以我们可以把5,6,4这三个矩形合并成一个高度为4,宽度为3的矩形,使得整个序列单调递增.
代码(fromPOJ 2559 &&HDU 1506 Largest Rectangle in a Histogram && 51nod 1102 面积最大的矩形 单调栈的应用 - zuzhiang的博客 - CSDN博客 ):
#include<stdio.h>
#include<string.h>
#include<iostream>
#include<stack>
using namespace std;
typedef long long LL;
int main()
{
int i,n,top; //top指向栈顶
stack<int> st; //栈用于保存矩形的编号,即位置
LL tmp,ans,a[100010]; //tmp为临时变量,记录面积的值,ans为结果,记录最大面积值
while(~scanf("%d",&n)&&n)
{
for(i=0;i<n;i++)![IMG_20181212_141702.jpg](attachments\6e2e9506.jpg)
scanf("%lld",&a[i]);
ans=0;
a[n]=-1; //最后一个元素设为最小值,以最后清空栈
for(i=0;i<=n;i++)
{
if(st.empty()||a[i]>=a[st.top()])
{ //如果栈为空或入栈元素大于等于栈顶元素 ,则入栈
st.push(i);
}
else
{
while(!st.empty()&&a[i]<a[st.top()])
{ //如果栈非空且入栈元素小于栈顶元素,则将栈顶元素出栈
top=st.top();
st.pop();
tmp=(i-top)*a[top]; //在出栈过程中计算面积值
if(tmp>ans) ans=tmp; //更新面积最大值
}
st.push(top); //只将可以延伸到的最左端的位置入栈
a[top]=a[i]; //并修改该位置的值
/**此处实际也是高度大的矩形合并为一个高度相同长度大于1的矩形
*/
}
}
printf("%lld\n",ans);
}
return 0;
}
POJ 3494 – Largest Submatrix of All 1’s
题目大意:求仅由0,1组成的矩阵中,全部由1组成的子矩阵的最大面积。
参考前辈博客:POJ 3494 Largest Submatrix of All 1’s 单调栈应用 图解+代码详解 - zuzhiang的博客 - CSDN博客
用处3对应题目:
POJ 2796 – Feel Good
题意:给出一个序列,求出一个子序列,使得这个序列中的最小值乘以这个序列的和的值最大。
思路:直接用单调栈解决即可,由于维护单调栈的过程中会改变原数组的值,所以需要加一个sum数组保存前缀和,也方便计算区间元素和。
大佬代码;
#include<stdio.h>
#include<iostream>
#include<stack>
using namespace std;
typedef long long LL;
int main()
{
int i,n,pos1,pos2; //pos1和pos2记录区间的开始和结束位置
//tmp为临时变量,记录区间内的和;top指向栈顶元素;ans为结果;sum为前缀和
LL tmp,top,ans,a[100010],sum[100010];
stack<int> st; //单调栈,记录元素位置
while(~scanf("%d",&n))
{
while(!st.empty()) st.pop(); //清空栈
sum[0]=0;
for(i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
sum[i]=sum[i-1]+a[i]; //计算前缀和
}
a[n+1]=-1; //将最后一个设为最小值,以最后让栈内元素全部出栈
ans=0;
for(i=1;i<=n+1;i++)
{
if(st.empty()||a[i]>=a[st.top()])
{ //如果栈为空或入栈元素大于等于栈顶元素,则入栈
st.push(i);
}
else
{
while(!st.empty()&&a[i]<a[st.top()])
{ //如果栈非空并且入栈元素小于栈顶元素,则将栈顶元素出栈
top=st.top();
st.pop();
tmp=sum[i-1]-sum[top-1]; //计算区间内元素和
tmp*=a[top]; //计算结果
/*
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
a[top]始终为a[10010]在区间[st.top(), i )上的最小值。
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
*/
if(tmp>=ans)
{ //更新最大值并记录位置
ans=tmp;
pos1=top;
pos2=i;
}
}
st.push(top); //将最后一次出栈的栈顶元素入栈
a[top]=a[i]; //将其向左向右延伸并更新对应的值
}
}
printf("%lld\n",ans);
printf("%d %d\n",pos1,pos2-1);
}
return 0;
}
单调栈的实质:
对于非递减单调栈:
设原序列为 l[n],在此基础上修改后的为a[n],栈为s,假设对栈可随机访问,则对于栈内任一元素s[m]和s[m+1],a[s[m]]为原序列 l 在区间[ s[m] , s[m+1] ) 内的最小值。