文章目录
前言
单调队列介绍
在前面我们介绍了,单调队列使用方法 没有了解过的 可以去我的文章中查看
接下来 让我们 更加细致的去练习一下单调队列常用的套路
单调队列模板:
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
1∼k 个数,每次将木板向右移动一个单位,直到右端与第
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
k−1个数 要求 汇报数字的和 不能小于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 大法好!!!嘿嘿)