单调栈:
定义:用栈结构来实现,使得遍历数组中栈顶元素保持一定范围的最大或最小,并且栈中元素始终保持单调性的栈。
功能:用以快速(O(n))求出数组中某连续子集中的最大值或者最小值。
原理:
以求某连续子集中的最小值为例:
假设某数组为:
下标 | 1 | 2 | 3 | 4 | 5 | 6 |
元素 | 3 | 1 | 6 | 4 | 5 | 2 |
模拟过程:
栈顶下标表示当前元素为从此下标到 上一个栈顶下标的最小值。
1.栈为空,将3的下标压入栈中。 //当前栈状态:(栈底 0)1 (栈顶)
2.元素1 <= 3,所以3的下标出栈,1的下标入栈。 //当前栈状态:(栈底 0)2 (栈顶)
3.元素1 < 6,6的下标入栈。 //当前栈状态:(栈底 0) 2 3 (栈顶) 此时表示6为 2 + 1 到 3 的最小值。
4.元素4 <= 6,6的下标出栈,1 < 4,4的下标入栈。 //当前栈状态:(栈底0)2 4 (栈顶) 此时表示4为 2 + 1 到 4的最小值。
5.元素4 < 5,5的下标入栈。 //当前栈状态:(栈底0)2 4 5 (栈顶) 此时表示5为 4 + 1 到 5的最小值。
6.元素2 < 5, 5的下标出栈,2<4,4的下标出栈,2的下标入栈。 //当前栈状态:(栈底0)2 6 (栈顶) 此时表示2为 2 + 1 到 6的最小值。
7.更新完毕。
整个过程,每出栈一次记录一次,就可以记录下整个过程中某个连续子段的最小值,达到了其功能。
以下为例题。
poj 2796:
题意:
1e6的数据向量,求连续的子集的和与子集中最小的数的积的最大值, 并求出这段子集的左右坐标。
解析:
不造为啥用stack的stl错了,用数组的过了。
这题有两个注意的点,一个是开始ans置为-1,不然会wa的很惨很惨,因为ans = 0 的时候也更新ansL, ansR。
另一个就是要把-1的节点插入到数组最后,以便单调栈遍历完整个数组。
栈顶为当前的最小值。
代码:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <stack>
#include <vector>
#include <queue>
#include <map>
#include <climits>
#include <cassert>
#define LL long long
#define lson lo, mi, rt << 1
#define rson mi + 1, hi, rt << 1 | 1
using namespace std;
const int maxn = 1e5 + 10;
const int inf = 0x3f3f3f3f;
const double eps = 1e-8;
const double pi = acos(-1.0);
const double ee = exp(1.0);
LL a[maxn];
LL sum[maxn];
int s[maxn];
int main()
{
#ifdef LOCAL
freopen("in.txt", "r", stdin);
#endif // LOCAL
int n;
while (~scanf("%d", &n))
{
memset(sum, 0, sizeof(sum));
for (int i = 1; i <= n; i++)
{
scanf("%lld", &a[i]);
sum[i] += sum[i - 1] + a[i];
}
a[++n] = -1;///debug
LL ans = -1;///debug
int ansL = -1, ansR = -1;
int top = 0;
for (int i = 1; i <= n; i++)
{
while (top && a[i] <= a[s[top]])
{
LL t = a[s[top]] * (sum[i - 1] - sum[s[top - 1] + 1 - 1]);
if (ans < t)
{
ans = t;
ansL = s[top - 1] + 1;
ansR = i - 1;
}
top--;
}
s[++top] = i;
}
printf("%lld\n%d %d\n", ans, ansL, ansR);
}
return 0;
}
poj 2559:
题意:
如上图,给一些宽度为1的矩形的高度,求由矩形围成的大矩形的最大面积。
数据量1e5。
解析:
找每个范围中的最小高度,此时这个最小高度作为整个范围的高,计算面积,扫一遍取最大面积即可。
往前扫一遍,后扫一遍,相当于记录当前这个点a[i]在哪个区域内始终是最小的。
注意的是后扫的时候,要将栈顶置为-1,否则后扫时栈值无法置为n+1。
比如 5 0 0 0 0 1,这组数据。
代码:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <stack>
#include <vector>
#include <queue>
#include <map>
#include <climits>
#include <cassert>
#define LL long long
#define lson lo, mi, rt << 1
#define rson mi + 1, hi, rt << 1 | 1
using namespace std;
const int maxn = 1e5 + 10;
const int inf = 0x3f3f3f3f;
const double eps = 1e-8;
const double pi = acos(-1.0);
const double ee = exp(1.0);
LL a[maxn];
int s[maxn];//stack
int pre[maxn];
int las[maxn];
int main()
{
#ifdef LOCAL
freopen("in.txt", "r", stdin);
#endif // LOCAL
int n;
while (~scanf("%d", &n) && n)
{
memset(s, 0, sizeof(s));
for (int i = 1; i <= n; i++)
{
scanf("%lld", &a[i]);
}
int top = 0;
a[0] = -1;
for (int i = 1; i <= n; i++)
{
while (top && a[i] <= a[s[top]])
{
top--;
}
pre[i] = s[top];
s[++top] = i;
}
top = 0;
s[top++] = n + 1;
a[n + 1] = -1;
for (int i = n + 1; i >= 1; i--)
{
while (top && a[i] <= a[s[top]])
{
top--;
}
las[i] = s[top];
s[++top] = i;
}
LL ans = 0;
for (int i = 1; i <= n; i++)
{
// cout << pre[i] << " " << las[i] << endl;
LL t = (las[i] - pre[i] - 1) * a[i];
if (ans < t)
ans = t;
}
printf("%lld\n", ans);
}
return 0;
}
CF 547B:
题目:
Description
Mike is the president of country What-The-Fatherland. There are n bears living in this country besides Mike. All of them are standing in a line and they are numbered from1 ton from left to right.i-th bear is exactlyai feet high.

A group of bears is a non-empty contiguous segment of the line. The size of a group is the number of bears in that group. The strength of a group is the minimum height of the bear in that group.
Mike is a curious to know for each x such that1 ≤ x ≤ n the maximum strength among all groups of sizex.
Input
The first line of input contains integer n (1 ≤ n ≤ 2 × 105), the number of bears.
The second line contains n integers separated by space,a1, a2, ..., an (1 ≤ ai ≤ 109), heights of bears.
Output
Print n integers in one line. For each x from 1 to n, print the maximum strength among all groups of size x.
Sample Input
10 1 2 3 4 5 4 3 2 1 6
6 4 4 3 3 2 2 1 1 1
题意:
给n个数,找到长度为1 ~ n 的子集中,每个子集中元素的最小值,然后取代表每个子集的最小值的最大值。
解析:
如上题一样,前后扫一遍,相减出长度。
然后扫一遍就好啦。
感谢这题让我认识了单调栈!
这题有个要注意的地方:
所有长度扫完之后要做一个下面的更新:
for (int i = n - 1; i >= 1; i--)
{
maxValue[i] = max(maxValue[i + 1], maxValue[i]);
}
因为有可能一些长度是取不到的,所以酱紫更新一下就行了。
代码:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <stack>
#include <vector>
#include <queue>
#include <map>
#include <climits>
#include <cassert>
#define LL long long
#define lson lo, mi, rt << 1
#define rson mi + 1, hi, rt << 1 | 1
using namespace std;
const int maxn = 2e5 + 10;
const int inf = 0x3f3f3f3f;
const double eps = 1e-8;
const double pi = acos(-1.0);
const double ee = exp(1.0);
int a[maxn];
int pre[maxn], las[maxn];
int maxValue[maxn];
int main()
{
#ifdef LOCAL
freopen("in.txt", "r", stdin);
#endif // LOCAL
int n;
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
}
stack<int> s;
s.push(0);
for (int i = 1; i <= n; i++)
{
while (!s.empty() && a[i] <= a[s.top()])
s.pop();
pre[i] = s.top();
s.push(i);
}
stack<int> t;
t.push(n + 1);
for (int i = n; i >= 1; i--)
{
while (!t.empty() && a[i] <= a[t.top()])
{
t.pop();
}
las[i] = t.top();
t.push(i);
}
for (int i = 1; i <= n; i++)
{
// cout << pre[i] << " " << las[i] << endl;
int len = las[i] - pre[i] - 1;
maxValue[len] = max(maxValue[len], a[i]);
}
for (int i = n - 1; i >= 1; i--)
{
maxValue[i] = max(maxValue[i + 1], maxValue[i]);
}
for (int i = 1; i <= n; i++)
{
printf("%d ", maxValue[i]);
}
return 0;
}