单调队列原理+练习

很早之前就学过了,现在系统的再学习下,做个汇总。

原理


deque实现,队头存最大值/最小值,从队头到队尾使之单调减/增。
以队头存最小值为例:

  • 队列为空,直接入队
  • 依次将队列中大于(等于也可以)当前值的数从队尾出队,最后再将当前值入队尾
deque<int> q;
for(int i = 0; i < n; i++)
{
	 while(!q.empty() && q.back()>a[i]) q.pop_back();
     q.push_back(a[i]);
}

练习


  • 洛谷P1886-滑动窗口-模版题
    传送门
    解题思路: 因为有长度k的限制,所以队列中存的是值的下标。在输出结果时要保证队头的元素在[i-k+1,i]内,即将队列中不再该范围内的值依次从队头取出,再输出当前的最大/小值。
    ac代码:
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int maxn = 1e6+10;
int a[maxn];
deque<int> q;//队头最大/小值
int n, k;
int main()
{
    //freopen("/Users/zhangkanqi/Desktop/11.txt","r",stdin);
    cin >> n >> k;
    for(int i = 0; i < n; i++) scanf("%d", &a[i]);
    for(int i = 0; i < n; i++)//队头存最小值
    {
        while(!q.empty() && a[q.back()]>a[i]) q.pop_back();
        q.push_back(i);
        if(i>=k-1)
        {
            while(!q.empty() && q.front()<=(i-k)) q.pop_front();//除去不在范围内的最小值
            printf("%d ",a[q.front()]);
        }
    }
    printf("\n");
    q.clear();
    for(int i = 0; i < n; i++)//队头存最大值
    {
        while(!q.empty() && a[q.back()]<a[i]) q.pop_back();
        q.push_back(i);
        if(i>=k-1)
        {
            while(!q.empty() && q.front()<=(i-k)) q.pop_front();
            printf("%d ", a[q.front()]);
        }
    }
    return 0;
}
  • 牛客1006D-最大子序列和
    传送门
    题目: 给定长度为n的整数序列,找到最大的长度不超过m的连续子序列和,n,m≤3e5
    解题思路: 固定子序列[j, i]的右端点i,那么 j [ i m + 1 , i ] j\in[i-m+1,i] j[im+1,i]且子序列[j,i]内的和利用前缀和可以转化为 s u m [ i ] s u m [ j 1 ] sum[i]-sum[j-1] sum[i]sum[j1]。i一定,sum[j-1]最小,连续子序列和最大。令J=j-1, J [ i m , i 1 ] J\in[i-m, i-1] J[im,i1]。在枚举i的过程中,J的区间每次右移一位,每次都在相应的区间内找最小值,这样这道题就转化成了洛谷P1886滑动窗口那道题,用单调队列来做,队头存最小值。
    注意队列里要先放一个0,因为i是先取到最小值(最小值是在队列非空的时候才取得到)后再存入。
    ac代码:
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int maxn = 3e5+10;
deque<int> q;//队头最大/小值
int n, m;
ll sum[maxn];
int main()
{
    //freopen("/Users/zhangkanqi/Desktop/11.txt","r",stdin);
    scanf("%d %d", &n, &m);
    for(int i = 1; i <= n; i++) scanf("%lld", &sum[i]), sum[i]+=sum[i-1];
    ll ans = LLONG_MIN;
    //在[i-m, i-1]中找最小的sum[J]
    q.push_back(0);
    for(int i = 1; i <= n; i++)
    {
        while(!q.empty() && sum[q.back()]>sum[i]) q.pop_back();
        while(!q.empty() && q.front()<i-m) q.pop_front();
        if(!q.empty()) ans = max(ans, sum[i]-sum[q.front()]);
        q.push_back(i);
    }
    printf("%lld\n", ans);
    return 0;
}
全部评论

相关推荐

Yushuu:你的确很厉害,但是有一个小问题:谁问你了?我的意思是,谁在意?我告诉你,根本没人问你,在我们之中0人问了你,我把所有问你的人都请来 party 了,到场人数是0个人,誰问你了?WHO ASKED?谁问汝矣?誰があなたに聞きましたか?누가 물어봤어?我爬上了珠穆朗玛峰也没找到谁问你了,我刚刚潜入了世界上最大的射电望远镜也没开到那个问你的人的盒,在找到谁问你之前我连癌症的解药都发明了出来,我开了最大距离渲染也没找到谁问你了我活在这个被辐射蹂躏了多年的破碎世界的坟墓里目睹全球核战争把人类文明毁灭也没见到谁问你了😆
点赞 评论 收藏
分享
沉淀一会:**圣经 1.同学你面试评价不错,概率很大,请耐心等待;2.你的排名比较靠前,不要担心,耐心等待;3.问题不大,正在审批,不要着急签其他公司,等等我们!4.预计9月中下旬,安心过节;5.下周会有结果,请耐心等待下;6.可能国庆节前后,一有结果我马上通知你;7.预计10月中旬,再坚持一下;8.正在走流程,就这两天了;9.同学,结果我也不知道,你如果查到了也告诉我一声;10.同学你出线不明朗,建议签其他公司保底!11.同学你找了哪些公司,我也在找工作。
点赞 评论 收藏
分享
点赞 收藏 评论
分享
牛客网
牛客企业服务