数据结构——单调栈,单调队列

参考来自:https://endlesslethe.com/monotone-queue-and-stack-tutorial.html#i-2

单调栈

一个单调递增栈的例子:
进栈元素分别为3,4,2,6,4,5,2,3

第i步操作结果
13进栈3
23出栈,4进栈4
32进栈4 2
42、4出栈,6进栈6
54进栈6 4
64出栈,5进栈6 5
72进栈6 5 2
82出栈,3进栈6 5 3

对于一个元素i:

  • 左边区间第一个比它大(小)的元素;
  • 确定这个元素是否为区间最值
  • 右边区间第一个大于它的值
  • 到右边区间第一个大于它的值的距离
  • 确定以该元素为最值得最长区间

代码

//在“尾部”添加元素
while (r != 0 || ms[r] <= x) r--;
ms[++r] = x;

//查询栈顶元素
if (r != 0) printf("%d\n", ms[r]);
else printf("-1");

这里可以把x换成元素x得下标压入栈

单调队列

一个递增单调队列的例子
队列大小不能超过3,入队元素依次为3,2,8,4,5,7,6,4

第i步操作结果
13入队3
23从队尾出队,2入队2
38入队2 8
48从队尾出队,4入队2 4
55入队2 4 5
62从队头出队,7入队4 5 7
77从队尾出队,6入队4 5 6
86、5、4从队尾出队,4入队4
  • 可以查询区间最值(不能维护区间第k大)
  • 优化DP

代码

//在“尾部”添加元素x
while (l != r && mq[r] <= x) r--;
mq[++r] = x;

//查询队首元素
if (l != r) printf("%d\n", mq[l+1]);
else printf("-1\n");

//弹出队首元素
if (l != r) l++;

下面直接见练习题(持续收集)

1. POJ 2823 Sliding Window

链接:POJ 2823
**题意:**就是给你n 和 k,表示元素的个数, 以及区间长度, 要求输出从1到n-k为起始下标的每个长度为k的区间的最小值输出,以及最大值输出。
**题解:**这里用单调队列写,记录的是下标,并且判断一下当前的最值的下标在不在当前的区间内就可,具体看代码。
代码:

//#include <bits/stdc++.h>
#include <cstdio>
#include <iostream>

using namespace std;

const int maxn = 1e6+50;
int n, len, l, r;
int a[maxn];
int mq[maxn];

int main()
{
    scanf("%d %d",&n, &len);
    for(int i = 1; i <= n; i++){
        scanf("%d", &a[i]);
    }
    ///1 3 -1 -3 5 3 6 7
    ///3 2 8 4 5 7 6 4
    l = 0, r = 0;
    for(int i = 1; i <= n; i++){
        while(l != r && a[mq[r-1]] >= a[i]){
            r--;
        }
        mq[r++] = i;
        while(mq[l] <= i - len) l++;
        //for(int j = l; j < r; j++)
        //    cout << a[mq[j]] << " ";
        //cout << endl;
        if(i >= len) cout << a[mq[l]] << " ";
    }
    cout << endl;
    l = 0, r = 0;
    for(int i = 1; i <= n; i++){
        while(l != r && a[mq[r-1]] <= a[i]) r--;
        mq[r++] = i;
        while(mq[l] <= i - len) l++;
        if(i >= len) cout << a[mq[l]] << " ";
    }
    cout << endl;
    return 0;
}

2.POJ 3250 Bad Hair Day

链接:POJ 3250
**题意:**给你n头奶牛的高度,从左到右依次排列,都向右看,现在要你求每一头奶牛向右看可以看到的奶牛总数。
**题解:**单调队列简单题
代码:

#include <iostream>
#include <cstdio>

using namespace std;

const int maxn = 1e6+60;
long long n, a[maxn];
long long h[maxn], w[maxn], cur = 0;
long long ans = 0;
const int INF = 0x3f3f3f3f;

void solve()
{
    h[0] = INF+1;
    for(int i = 1; i <= n+1; i++)
    {
        if(a[i] < h[cur])
        {
            h[++cur] = a[i];
            w[cur] = cur-1;
        }
        else
        {
            while(a[i] >= h[cur])
            {
                ans += w[cur];
                h[cur--] = 0;
                if(cur == 0)
                    break;
                //cur--;
            }
            h[++cur] = a[i];
            w[cur] = cur-1;
        }
        if(cur == 0)
            return;
    }
}

int main()
{
    scanf("%lld", &n);
    for(int i = 1; i <= n; i++)
        scanf("%lld", &a[i]);
    a[n+1] = INF;
    solve();
    cout << ans << endl;
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值