C++单调队列模板+基础题题单+习题讲解


前言

单调队列介绍

在前面我们介绍了,单调队列使用方法 没有了解过的 可以去我的文章中查看
接下来 让我们 更加细致的去练习一下单调队列常用的套路

单调队列模板:

 	cin>>n>>k; //输入序列的长度和窗口的长度
    for(int i=1;i<=n;i++) cin>>a[i];
    //单调递减队列模板
    for(int i=1;i<=n;i++){
    	//只要脱离了窗口就弹出
        while(!q.empty()&&q.front()<=i-k)q.pop_front();
        //只要队尾元素大于当前元素 不满足单调性 弹出 并插入当前元素
        while(!q.empty()&&a[q.back()]>a[i])q.pop_back();
        q.push_back(i);
        //如果窗口满了 就输出
        if(!q.empty()&&i>=k)cout<<a[q.front()]<<" ";
    }

二、单调队列习题:

1.单调队列题单:

单调队列 - 题单 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

2.习题讲解(附代码):

2.1 [NOIP2010 提高组] 机器翻译

题目大意:
给你两个整数 M M M, N N N 代表机器容量和文章的长度,刚开始机器没有存任何单词,现在给你一个单词要你查出翻译,如果机器内存中有就从内存中取,如果没有就去外存找,若内存中已存入 M M M 个单词,软件会清空最早进入内存的那个单词,腾出单元来,存放新单词,要你求从外存读取的次数

思路: 这道题思路很明确了 软件会清空最早进入内存的那个单词 毫无疑问解这个题要用到队列的性质 ,首先用一个bool数组记录一下当前单词存不存在,若存在就跳过,若不存在,就要从外存去找,答案次数加一,并将当前单词放入队列,bool 数组更新为true,如果满了那就 bool数组更新为 false 并且弹出队首

AC代码:

#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#define int long long 
using namespace std;
const int N = 1010;
queue<int> q;
bool hashset[N];
int m,n;
signed main(){
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    cin>>m>>n;
    int cnt=0;
    while(n--){
        int x;cin>>x;
        if(!hashset[x]){ //如果不存在
            cnt++; //答案次数加一
            q.push(x); //放入队列
            hashset[x]=true; //状态更新为true
            while(q.size()>m){ //如果满了 更新状态并弹出
                hashset[q.front()] =false;
                q.pop();
            }
        }
    }
    cout<<cnt;
    return 0;
}

2.2 滑动窗口 /【模板】单调队列

这是一道板子题,详细解法,可以看我上一篇文章
单调队列模板题讲解
这里附上AC代码:

#include <iostream>
#include <cstring>
#include <algorithm>
#include <deque>

using namespace std;
const int N = 1e6+10;
int n,k;
int a[N];
deque<int> q;
int main(){
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    cin>>n>>k;
    for(int i=1;i<=n;i++) cin>>a[i];
    
    for(int i=1;i<=n;i++){
        while(!q.empty()&&q.front()<=i-k)q.pop_front();
        while(!q.empty()&&a[q.back()]>a[i])q.pop_back();
        q.push_back(i);
        if(!q.empty()&&i>=k)cout<<a[q.front()]<<" ";
    }
    while(!q.empty())q.pop_back();
    cout<<"\n";
    
    for(int i=1;i<=n;i++){
        while(!q.empty()&&q.front()<=i-k)q.pop_front();
        while(!q.empty()&&a[q.back()]<a[i])q.pop_back();
        q.push_back(i);
        if(!q.empty()&&i>=k)cout<<a[q.front()]<<" ";
    }
    return 0;
}

2.3 求m区间内的最小值

题目大意:
一个含有 n n n 项的数列,求出每一项前的 m m m 个数到它这个区间内的最小

值若前面的数不足 m m m项则从第 1 个数开始,若前面没有数

则输出 0。

思路: 题目说了 m m m 大于1 那么第一个元素前面肯定没有数直接输出 0
然后从第二个数开始,每次窗口右边界到当前元素的前面一个数,这里是窗口没满也要输出
AC代码:

#include <iostream>
#include <cstring>
#include <algorithm>
#include <deque>
using namespace std;
const int N = 2e6+10;
int n,m;
int a[N];
deque<int> q;
int main(){
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    cin>>n>>m;
    for(int i=1;i<=n;i++)cin>>a[i];
    //第一个直接输出 0
    cout<<0<<"\n";
    //从第二个开始 
    for(int i=2;i<=n;i++){
        while(!q.empty()&&q.front()<=i-1-m)q.pop_front();
        while(!q.empty()&&a[q.back()]>a[i-1])q.pop_back();
        q.push_back(i-1);
        //这里窗口没满也要输出
        if(!q.empty())cout<<a[q.front()]<<"\n";
    }
    
}

2.4 扫描

题目大意:
有一个 1 × n 1 \times n 1×n 的矩阵,有 n n n 个整数。现在给你一个可以盖住连续 k k k 个数的木板。一开始木板盖住了矩阵的第 1 ∼ k 1 \sim k 1k 个数,每次将木板向右移动一个单位,直到右端与第 n n n 个数重合。每次移动前输出被覆盖住的数字中最大的数是多少。

思路: 还是道板子题,之前都是求最小值,现在换成了最大值

AC代码:

#include <iostream>
#include <cstring>
#include <algorithm>
#include <deque>

using namespace std;
const int N =  2e6+10;
int a[N],n,k;
deque<int> q;
int main(){
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    cin>>n>>k;
    for(int i=1;i<=n;i++) cin>>a[i];
    
    for(int i=1;i<=n;i++){
        while(!q.empty()&&q.front()<=i-k)q.pop_front();
        while(!q.empty()&&a[q.back()]<a[i])q.pop_back();
        q.push_back(i);
        if(!q.empty()&&i>=k)cout<<a[q.front()]<<"\n";
    }
    
    return 0;
}

2.5 好消息,坏消息

题目大意:
给你n个数字 , 你只能按照顺序汇报,但是可以中间进行 第 k k k个位置向后汇报然后 倒叙 到第 k − 1 k-1 k1个数 要求 汇报数字的和 不能小于0

比如: -3 5 1 2
可行的方案是 5 1 2 -3 或者 1 2 -3 5

思路: 你会发现这个序列 有类似环这样的属性 ,直接处理是不好处理的这里我们要引入一个思想 化环为链 直接在后面接一个变成 -3 5 1 2 -3 5 1 细心的同学会发现少了最后一个2 这是为什么呢? 这主要是为了 防止重复计算, 本题还要利用前缀和用来求区间和,所以每次要保证队首前缀和最大才可能计算出值大于0的情况

比如说 : 这样一个情况 第一种是可以满足的 但是!!!
在这里插入图片描述
在这里插入图片描述

这样又会多算一种情况所以不能到最后

AC代码

#include <iostream>
#include <cstring>
#include <algorithm>
#include <deque>
using namespace std;
const int N = 2e6+20;
int a[N],s[N];
int n,ans;
deque <int> q;
int main(){
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i];
    //化环为链
    for(int i=n+1;i<=n+n-1;i++) a[i]=a[i-n];
    //前缀和预处理
    for(int i=1;i<=n+n-1;i++) s[i]=s[i-1]+a[i];

    for(int i=1;i<=n+n-1;i++){
        while(!q.empty()&&q.front()<=i-n)q.pop_front();
        //保证队首前缀和最大
        while(!q.empty()&&s[q.back()]>s[i])q.pop_back();
        q.push_back(i);
        //这里的意思 窗口中的区间求和 比如求l~r 的区间和 s[r]-s[l-1] 这里也是这个意思
        if(i>=n&&s[q.front()]-s[i-n]>=0)ans++;
    }
    cout<<ans;
    return 0;
}

总结:

做完以上题目,单调队列基础其实就没什么问题了(STL 大法好!!!嘿嘿)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值