单调队列
单调队列就是队列中元素满足单调性
入队:从队尾入队,在入队的时候删掉队尾比当前入队的元素大(或小)的元素
出队:出队是直接把队头元素取出
例一
- 题目大意
给定一个长为n数组以及一个长为k的滑动窗口,窗口每个时刻向后移动一位,求出每个时刻窗口中数字的最大值和最小值。
- 分析
最大值与最小值原理是一样的,以最小值为例。
暴力的做法复杂度是 O(nk)
我们希望找到 O(n) 的做法
线性 O(n) 的做法,一定会需要对某些信息进行保存(就是用空间来换时间的做法)。我们要找的是当前窗口的最小值,我们发现当前窗口的最小值在窗口移动的过程中有可能还是为最小值,那能不能将当前窗口的最小值记录下在在窗口移动加入新元素的时候取更新这个最小值呢,好像有点道理,但仔细思考后发现这样是不对的,如果这个最小值在窗口移动的过程中跑出窗口外了就不好处理了,因为我们并没有记录当前窗口内的次小值,按照这个思路走下去,我们发现我们需要顺序地保存前k小的值,这个时候可以用优先队列来维护复杂度是 O(nlog(k)) ,效率还是不够高。
通过观察我们可以发现,如果后进来的元素比框中之前的某个元素小,那么框中的这个元素就一定是没用的。
根据这一条启发式信息我们就可以去优化之前的优先队列的方法了。我们需要去掉框中一定没用的元素,每当进来一个元素我们就去更新队列里面比它大的元素(因为我们希望找到线性的方法,所以这个队列一定是有序的),这样在新入队一个元素的时候删掉队尾比这个元素大的元素从而维护了队列的单调性,单调队列名称由此而来。 - 代码
#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<queue>
#include<algorithm>
using namespace std;
const int INF=999999999;
const int MAXN=1000005;
int a[MAXN];
int c[MAXN];//c[i]表示队列中的第i个数在数组a中的位置
int minm[MAXN];
int maxm[MAXN];
int n,m;
int st,en;
void get_min()
{
c[1]=1;
st=1;en=1;
minm[1]=a[1];
for(int i=2;i<=n;i++)
{
while(a[i]<=a[c[en]] && en>=st)en--;
c[++en]=i;
if(i-c[st]+1>m)st++;
minm[i]=a[c[st]];
}
}
void get_max()
{
c[1]=1;
st=1;en=1;
maxm[1]=a[1];
for(int i=2;i<=n;i++)
{
while(a[i]>=a[c[en]] && en>=st)en--;
c[++en]=i;
if(i-c[st]+1>m)st++;
maxm[i]=a[c[st]];
}
}
int main()
{
int st=1,en=1;
int t=0;
while(scanf("%d%d",&n,&m)!=EOF)
{
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
get_min();
get_max();
for(int i=m;i<=n;i++)
cout<<minm[i]<<" ";
cout<<minm[n]<<endl;
for(int i=m;i<=n;i++)
cout<<maxm[i]<<" ";
cout<<maxm[n]<<endl;
}
return 0;
}
例二
- 题目大意
给你三个数X,k,t 每次可以进行以下两种操作之一:
1:X=X−i,(0≤i≤t)
2.如果k|X,X=Xk
(0≤t≤106,1≤X,k≤106)
问最少经过多少次操作能从X变到1
- 分析
很明显的一道动态规划题,以每个数到1所需的次数作为状态,但这样发现状态有 106 种,在每一次转移中最多进行 106 次操作,这样肯定超时,所以需要做一些优化或者改变思路。
我们容易找到动态规划转移方程:
{f[i]=min(f[i],f[i/k]+1)f[i]=min(f[i],f[i−t...i−1])if(i%k==0)
而单调队列正好可以用来优化形如 f[i]=min(f[k]+g[i])的方程 - 代码
#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<queue>
using namespace std;
const int INF=999999999;
const int MAXN=1000005;
int T;
int X,k,t;
int p[MAXN];//目标函数的单调队列
int pos[MAXN];//pos[i]表示队列中第i个元素在f中的位置
int f[MAXN];//状态
int head,tail;
int main()
{
scanf("%d",&T);
while(T--)
{
scanf("%d%d%d",&X,&k,&t);
memset(f,127,sizeof(f));
f[1]=0;
head=1;tail=1;
p[tail]=f[1];
pos[tail]=1;
for(int i=2;i<=X;i++)
{
if(tail>head && pos[head]<(i-t) )head++;//把出界的元素扔出去,因为有t=0的情况所以需要tail>head
if(i%k==0) f[i]=min(f[i],f[i/k]+1);
f[i]=min(f[i],p[head]+1);
while(tail>=head && f[i]<=p[tail]){tail--;}
p[++tail]=f[i];pos[tail]=i;
}
cout<<f[X]<<endl;
}
}