1.单调队列(解决滑动窗口问题)
求长度为k的滑动窗口内的最大/小值
单调队列中的数具有单调性
例如求最大值时,队列左端的数最大,右端的数最小
1.当第i个数进入滑动窗口时,首先判断队列左端的数是否还在窗口内(拿窗口左端下标和队列中的下标对比)
2.判断新来的数是否能替换单调队列中的数,从队列右端开始比较,比如求最大值时,如果新来的数比队列右端的数大,则弹出队列右端,直到队列空或不能替换,将新数插入队列
下面是求滑动窗口最小值的代码,最大值的话将第二步的大小符号调换即可
int hh=0,tt=-1;
//此时第i个数进入滑动窗口
//窗口的头为i-k+1
for(int i=0;i<n;i++)
{
//队列里最大/小值已经不在窗口内了
//即窗口的头坐标>队列中最值的坐标
//队列中最值更替,hh++
if(hh<=tt&&i-k+1>q[hh]) hh++;
//把新进窗口的数和队列里的数对比
//从队尾开始,可以更替就删除队尾tt--
while(hh<=tt&&a[q[tt]]>=a[i]) tt--;
//把新的数放到队列合适的位置
q[++tt]=i;
if(i-k+1>=0) cout<<a[q[hh]]<<" ";
}
2.单调栈
求一个数左边第一个比他小/大的数
和单调队列相同,单调栈中的元素也具有单调性
例如求左边离他最近的较小值时,单调栈从栈低到栈顶递增
每处理一个新数时,将其与栈顶对比并且弹出,直到栈顶比新数小,将新数压入栈中
由于之前弹出的数一定比栈顶的数要大,所以不会影响之后读入的数的离他最近的最小值
#include<bits/stdc++.h>
using namespace std;//单调栈
//用于找出序列中每个数左边离它最近的比它大/小的数
int stk[100005],tt;
int main()
{
int n;cin>>n;
for(int i=0;i<n;i++)
{
int x;cin>>x;
while(tt&&stk[tt]>=x)//栈不空,且栈顶不小于当前数
tt--;
if(tt) cout<<stk[tt]<<" ";
else cout<<"-1"<<" ";
stk[++tt]=x;
}
}
最大子序和
滑动窗口维护最小的前缀和,由于是前缀和,窗口大小要扩大一位,且初始把0加入
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int n,m;
int w[N];
int q[N];
int res=-0x3f3f3f3f;
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>w[i];
w[i]+=w[i-1];
}
int hh=0,tt=-1;
q[++tt]=0;//前缀和,s[0]=0
for(int i=1;i<=n;i++){//由于是前缀和,需要把窗口扩大一位
if(hh<=tt&&i-q[hh]+1>m+1) hh++;
res=max(res,w[i]-w[q[hh]]);
while(hh<=tt&&w[i]<=w[q[tt]]) tt--;
q[++tt]=i;
}
cout<<res;
}
烽火传递
表示对于前i个烽火台,在第i个烽火台发信号的最小代价
由于连续m个烽火台至少有一个发信号,当第i个烽火台发信号时,需要[i-m,i-1]之间至少有一个烽火台发信号,即i的前m个f[i]的最小值,单调队列需要维护的窗口大小为m+1
为了保证最后连续m个烽火台中有发信号的,答案需要在f[n-m+1,n]中取最小值
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int n,m;
int w[N];
int q[N];
int f[N];//f[i] 前i个,在i发信号,最小花费
int hh=0,tt=-1;
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>w[i];
}
q[++tt]=0;
//维护f[i]最小的
for(int i=1;i<=n;i++){
if(hh<=tt&&i-q[hh]+1>m+1) hh++;
f[i]=f[q[hh]]+w[i];
while(hh<=tt&&f[i]<=f[q[tt]]) tt--;
q[++tt]=i;
}
//保证最后m个中有发送的
int res=1e8;
for(int i=n-m+1;i<=n;i++){
res=min(res,f[i]);
}
cout<<res;
}