单调队列定义:
其实单调队列就是一种队列内的元素有单调性的队列,因为其单调性所以经常会被用来维护区间最值或者降低DP的维数已达到降维来减少空间及时间的目的。
单调队列的一般应用:
1.维护区间最值
2.优化DP
例题引入:
https://www.luogu.org/problemnew/show/P1440
一个含有n项的数列(n<=2000000),求出每一项前的m个数到它这个区间内的最小值。若前面的数不足m项则从第1个数开始,若前面没有数则输出0。
例题解答:
首先看到题目可以很快想到O(NM),对于2*10^6这样的数据无疑要TLE的;
接下来考虑用单调队列,因为每一个答案只与当前下标的前m个有关,所以可以用单调队列维护前m的个最小值,
考虑如何实现该维护的过程??
显然当前下标X的m个以前的元素(即下标小于X-M+1的元素)肯定对答案没有贡献,所以可以将其从单调队列中删除。
对于两个元素A,B,下标分别为a,b,如果有A>=B&&a<b那么B留在队列里肯定优于A,因此可以将A删除。
维护队首:如果队首已经是当前元素的m个之前,将head++,弹出队首元素
维护队尾:比较q[tail]与当前元素的大小,若当前元素更优tail++,弹出队尾元素,直到可以满足队列单调性后加入当前元素。
考虑单调队列的时间复杂度:由于每一个元素只会进队和出队一次,所以为O(N)。
一般建议用数组模拟单调队列进行操作,而不用系统自带的容器,因为系统自带容器不易调试且可能有爆空间的危险。
代码实现:
#include<bits/stdc++.h> using namespace std; #define re register int #define INF 0x3f3f3f3f #define ll long long #define maxn 2000009 #define maxm inline ll read() { ll x=0,f=1;char ch=getchar(); while(ch<‘0‘||ch>‘9‘){if(ch==‘-‘) f=-1;ch=getchar();} while(ch>=‘0‘&&ch<=‘9‘){x=(x<<1)+(x<<3)+(ll)(ch-‘0‘);ch=getchar();} return x*f; } int n,m,k,tot,head,tail; int a[maxn],q[maxn]; int main() { // freopen(".in","r",stdin); // freopen(".out","w",stdout); n=read(),m=read(); for(int i=1;i<=n;i++) a[i]=read(); head=1,tail=0;//起始位置为1 因为插入是q[++tail]所以要初始化为0 for(int i=1;i<=n;i++)//每次队首的元素就是当前的答案 { printf("%d\n",a[q[head]]); while(i-q[head]+1>m&&head<=tail)//维护队首 head++; while(a[i]<a[q[tail]]&&head<=tail)//维护队尾 tail--; q[++tail]=i; } // fclose(stdin); // fclose(stdout); return 0; }
习题报告:
滑动窗口:https://www.luogu.org/problemnew/show/P1886
解题思路: 此题与例题相同,只是所要求的是最大值和最小值,只需要做两遍单调队列即可
#include<bits/stdc++.h> using namespace std; #define re register int #define ll long long #define maxn 1000009 #define maxm inline ll read() { ll x=0,f=1;char ch=getchar(); while(ch<‘0‘||ch>‘9‘){if(ch==‘-‘)f=-1;ch=getchar();} while(ch>=‘0‘&&ch<=‘9‘){x=(x<<1)+(x<<3)+(ll)(ch-‘0‘);ch=getchar();} return x*f; } int q[maxn],a[maxn]; int n,m,k,ans,tot,head,tail; void Ask_MIN() { head=1,tail=0; for(int i=1;i<=n;i++) { while(head<=tail&&i-q[head]+1>m) head++; while(head<=tail&&a[q[tail]]>=a[i]) tail--; q[++tail]=i; if(i>=m) printf("%d ",a[q[head]]); } puts(""); } void Ask_MAX() { head=1,tail=0; for(int i=1;i<=n;i++) { while(head<=tail&&i-q[head]+1>m) head++; while(head<=tail&&a[q[tail]]<=a[i]) tail--; q[++tail]=i; if(i>=m) printf("%d ",a[q[head]]); } puts(""); } int main() { // freopen(".in","r",stdin); // freopen(".out","w",stdout); n=read(),m=read(); for(int i=1;i<=n;i++) a[i]=read(); Ask_MIN(); Ask_MAX(); fclose(stdin); fclose(stdout); return 0; }
挤奶牛:https://www.luogu.org/problemnew/show/P3088
解题思路:此题题目需要维护左和右分别D区间内的最大值,因此可以正着和倒着分别做一次单调队列,然后打标记即可。
#include<bits/stdc++.h> using namespace std; #define re register int #define ll long long #define maxn 50009 #define maxm inline ll read() { ll x=0,f=1;char ch=getchar(); while(ch<‘0‘||ch>‘9‘){if(ch==‘-‘)f=-1;ch=getchar();} while(ch>=‘0‘&&ch<=‘9‘){x=(x<<1)+(x<<3)+(ll)(ch-‘0‘);ch=getchar();} return x*f; } struct cow { int h,x; }p[maxn]; int q[maxn]; bool fear[maxn]; int n,m,k,ans,tot,head,tail; bool comp(cow a,cow b) { return a.x<b.x; } int main() { // freopen(".in","r",stdin); // freopen(".out","w",stdout); n=read(),m=read(); for(int i=1;i<=n;i++) p[i].x=read(),p[i].h=read(); sort(p+1,p+1+n,comp); head=1,tail=0; for(int i=1;i<=n;i++) { while(head<=tail&&p[i].x-p[q[head]].x>m) head++; while(head<=tail&&p[i].h>=p[q[tail]].h) tail--; q[++tail]=i; if(p[q[head]].h>=2*p[i].h) fear[i]=1; } head=1,tail=0; for(int i=n;i>=1;i--) { while(head<=tail&&p[q[head]].x-p[i].x>m) head++; while(head<=tail&&p[q[tail]].h<=p[i].h) tail--; q[++tail]=i; if(p[q[head]].h>=p[i].h*2&&fear[i]) ans++; } printf("%d\n",ans); fclose(stdin); fclose(stdout); return 0; }
单调队列优化DP:
切蛋糕:https://www.luogu.org/problemnew/show/P1714
解题思路:同最大连续和,需要求出的是一个最大的长度为K的区间和。要快速知道一个区间的和可以用前缀和相减的形式来得到,那么此题可以维护前缀和的单调性,保持
原文地址:https://www.cnblogs.com/Dxy0310/p/9742045.html