8.18-8.20单调队列单调栈

本文深入探讨了单调队列这一数据结构在解决计算机科学问题中的应用,包括直方图中最大矩形面积的计算、小组队列的模拟、双端队列的排序优化以及滑动窗口最大子序列和的求解。通过实例分析,展示了单调队列如何有效地处理区间限制和单调性问题,从而提高算法效率。
摘要由CSDN通过智能技术生成

单调栈

直方图中最大的矩形

在这里插入图片描述
主要思路:
分别求每个高度以自己为中心的大于等于这个高度的矩形个数,个数*高度即为面积,求解最大面积。
那么,高度应该怎么求呢?
可以来两边单调栈,存储一下最大边界,单调栈里边只存储最小的,若是栈顶的元素比当前的元素大,即弹出即可。(因为最小值控制着直方图的断开)

#include<iostream>
#include<algorithm>
#include<stack>
using namespace std;
typedef long long ll;
typedef pair<ll,int> pa;
const int N=1e5+10;
int n;
ll l[N],r[N],h[N];
void get(ll a[])
{
    stack<pa> q;
    h[0]=-1;
    q.push({-1,0});//防止栈空
    for(int i=1;i<=n;i++)
    {
        while(q.top().first>=h[i])
            q.pop();
        a[i]=i-q.top().second;
        q.push({h[i],i});
    }
}
int main()
{
    while(cin>>n&&n)
    {
        for(int i=1;i<=n;i++)
            cin>>h[i];
        get(l);
        reverse(h+1,h+n+1);
        get(r);
        ll ans=0;
        for(int i=1;i<=n;i++)
        {
           // cout<<r[n-i+1]+l[i]-1<<' '<<h[n-i+1]<<endl;
            ans=max(ans,(r[n-i+1]+l[i]-1)*h[n-i+1]);
        }
        cout<<ans<<endl;
    }
    return 0;
}

队列

小组队列

有 n个小组要排成一个队列,每个小组中有若干人。

当一个人来到队列时,如果队列中已经有了自己小组的成员,他就直接插队排在自己小组成员的后面,否则就站在队伍的最后面。

请你编写一个程序,模拟这种小组队列。

主要思路:
这个题目就是维护多个序列,妙在代码的实现上。
开一个队列数组,用【0】来存储不同序列的顺序。

#include<iostream>
#include<algorithm>
#include<queue>
#include<map>
using namespace std;

map<int,int> book;
int main()
{
    int t;
    int num=0;
    while(cin>>t&&t)
    {
        queue<int> q[1010];
        num++;
        cout<<"Scenario #"<<num<<endl;
        for(int i=1;i<=t;i++)
        {
            int n;
            cin>>n;
            while(n--)
            {
                int x;
                cin>>x;
                book[x]=i;
            }
        }
        string op;
        while(cin>>op)
        {
            if(op=="ENQUEUE")
            {
                int x;
                cin>>x;
                if(q[book[x]].empty())
                    q[0].push(book[x]);
                q[book[x]].push(x);
            }
            else if(op=="DEQUEUE")
            {
                int xx=q[0].front();
                cout<<q[xx].front()<<endl;
                q[xx].pop();
                if(q[xx].empty()) q[0].pop();
            }
            else if(op=="STOP")
            {
                cout<<endl;
                break;
            }
        }
    }
    return 0;
}

双端队列

达达现在碰到了一个棘手的问题,有 N

个整数需要排序。

达达手头能用的工具就是若干个双端队列。

她从 1到 N 需要依次处理这 N个数,对于每个数,达达能做以下两件事:

1.新建一个双端队列,并将当前数作为这个队列中的唯一的数;

2.将当前数放入已有的队列的头之前或者尾之后。

对所有的数处理完成之后,达达将这些队列按一定的顺序连接起来后就可以得到一个非降的序列。

请你求出最少需要多少个双端序列。

主要思路:
这个题目其实是考察双端队列的一个性质。cf上有一道题和这个有些类似。
分析双端队列,发现下标大的数只能存在最外侧,所以一个双端队列可以存一个单谷。
所以对值进行排序后,查看原来的下标值构成的单谷数即为答案数。
还有难点就是会有重复值,所以找出重复值的最大坐标和最小坐标进行分情况讨论,具体细节看代码。

#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
typedef pair<ll,ll> pa;
const int N=2e5+10;
pa a[N];
int main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i].first;
        a[i].second=i;
    }
    sort(a+1,a+1+n);
    bool book=0;
    ll pre=1e18;
    ll ans=1;
    for(int i=1;i<=n;i++)
    {
        int j=i+1;
        while(a[j].first==a[i].first)
            j++;
        int mina=a[i].second,maxa=a[j-1].second;
       // cout<<pre<<' '<<mina<<' '<<maxa<<endl;
        if(!book)
        {
            if(pre>maxa)
                pre=mina;
            else
            {
                pre=maxa;
                book=1;
            }
        }
        else if(book)
        {
            if(mina>pre)
                pre=maxa;
            else
            {
                book=0;
                ans++;
                pre=mina;
            }
        }
        i=j-1;
    }
    cout<<ans<<endl;
    return 0;
}

单调队列

滑动窗口

在这里插入图片描述
主要思路:
利用数组来模拟队列,实现单调队列。
与单调栈不同的是,不仅尾端要弹出,由于区间范围的限制,头部也要弹出。
分别进行两次单调队列的维护,一次最大值一次最小值。

#include<iostream>
#include<algorithm>
using namespace std;
const int N=1e6+10;
int a[N],q[N],n,m;
int main()
{
    cin>>n>>m;
    int h=0,t=-1;
    for(int i=0;i<n;i++)
    {
        cin>>a[i];
        if(i-q[h]>=m) h++;
        while(t>=h&&a[q[t]]>=a[i]) t--;
        q[++t]=i;
        if(i+1>=m) cout<<a[q[h]]<<' ';
    }
    cout<<endl;
    h=0,t=-1;
    for(int i=0;i<n;i++)
    {
        if(i-q[h]>=m) h++;
        while(t>=h&&a[q[t]]<=a[i]) t--;
        q[++t]=i;
        if(i+1>=m) cout<<a[q[h]]<<' ';
    }
    return 0;
}

最大子序和

输入一个长度为 n 的整数序列,从中找出一段长度不超过 m的连续子序列,使得子序列中所有数的和最大。

注意: 子序列的长度至少是 1。

主要思路:
一般有区间限制的都要使用单调队列,而不使用单调栈。
本题利用前缀和,只维护最小的区间左端点的前缀和。

#include<iostream>
#include<algorithm>
using namespace std;
const int N=3e5+10;
int s[N],q[N];
int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        cin>>s[i];
        s[i]+=s[i-1];
    }
    int ans=-1e9;
    int h=0,t=-1;
    for(int i=0;i<=n;i++)
    {
        if(t>=h&&i-q[h]>m) h++;
        if(i)ans=max(ans,s[i]-s[q[h]]);
        while(t>=h&&s[q[t]]>=s[i]) t--;
        q[++t]=i;
    }
    cout<<ans<<endl;
    return 0;
}

超市

超市里有 N 件商品,每件商品都有利润 pi 和过期时间 di,每天只能卖一件商品,过期商品不能再卖。

求合理安排每天卖的商品的情况下,可以得到的最大收益是多少。

主要思路:
其实这个问题主要是利用贪心的思想,这个问题有利用堆求解的解法,但是此处我使用并查集来进行问题的求解。
优先选择利益最大的商品卖出,且卖出日期在不过期的情况下越晚越好。
但是并查集在这里有什么作用呢?
主要是来进行时间的推移的,因为一个时间只能被一个商品占用,又因为只能早卖才能避免过期,所以每使用一个时间,就利用并查集将此时间等价于他的前一天,直至时间耗尽。

#include<iostream>
#include<algorithm>
using namespace std;
typedef pair<int,int> pa;
const int N=1e4+10;
int f[N];
pa a[N];
int find(int k)
{
    if(f[k]==k) return k;
    return f[k]=find(f[k]);
}
int main()
{
    int n;
    while(cin>>n)
    {
        int d=0;
        for(int i=1;i<=n;i++)
        {
            cin>>a[i].first>>a[i].second;
            d=max(d,a[i].second);
        }
        for(int i=1;i<=d;i++)
            f[i]=i;
        sort(a+1,a+n+1);
        int ans=0;
        for(int i=n;i>0;i--)
        {
            int r=find(a[i].second);
            if(r)
            {
                ans+=a[i].first;
                f[r]=r-1;
            }
        }
        cout<<ans<<endl;
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值