单调栈详解 + poj 2796 poj 2559 CF 547B

本文深入探讨了单调栈的概念、工作原理以及在解决数组问题时的高效应用,通过实例演示了如何利用单调栈求解最小值子集问题,包括连续子集的最小值求解、矩形面积最大值计算及子集最小值最大值问题。文章还提供了关键代码片段,展示了单调栈在实际编程中的应用,特别强调了数据处理和边界条件的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

单调栈:

定义:用栈结构来实现,使得遍历数组中栈顶元素保持一定范围的最大或最小,并且栈中元素始终保持单调性的栈。

功能:用以快速(O(n))求出数组中某连续子集中的最大值或者最小值。

原理:

以求某连续子集中的最小值为例:

假设某数组为:

下标123456
元素316452

模拟过程:

栈顶下标表示当前元素为从此下标到 上一个栈顶下标的最小值。

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

Input
10
1 2 3 4 5 4 3 2 1 6
Output
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;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值