说起这个话题,应该很多人会有一种似有所悟,但又不敢确定的感觉。
(我差不多就是那样)
没错,这正是因为其中“单调”一词的存在。
- 那么单调是什么?
- 学过函数的人都知道单调函数或者函数的单调性吧
- 其实直白一点说单调,就是一直增或一直减。
- eg:1,3,5,9就是一个单调增数列,数列中不存在后一个数比前一个数小的现象。
那么同样,在这里谈到的话题也有类似特点。
(一)单调队列
其实就是一个符合单调性质的队列,但它同时具有单调的性质以及队列的性质。
使用频率不算高,但却占有至关重要的地位。它的作用很简单,就是为了维护一组单调数据,让我们在运行的过程中能够快速寻求前k个或后k个中最大或最小的值。
(二)单调栈
就是一个符合单调性质的栈并且它具有单调的性质以及栈的性质。
上面的两个在作用方面是相同的,差别仅是在编程过程中维护的数组的方式不同
(单调队列有两个指针,分别代表头和尾;单调栈有一个指针,代表头指针)
下面举个简单的栗子来解释单调队列及单调栈。
eg:有一组数据:1,5,9,4,7,8,6,将他们依此输入。同时,在某一时刻会让你求出后n个数中的最大值。
根据题意,我们可以得出这样一个结论:
若后一个数大于前一个数,则结果必定不会是前一个数
(比如现在输入了1,5,由于1<5,所以无论是后几个数中的最大值均不会为1)。
因此,我们只需维护一个单调递减的数组便可快速求得所需值。
其中数组变化如下:
输入——1,数组——1; 输入——5,由于5>1删去1添入5,数组——5; 输入——9,由于9>5删去5添入9,数组——9; 输入——4,由于4<9直接添入,数组——9,4; 输入——7,由于7>4同时7<9因此删去4添入7,数组——9,7; 输入——8,由于8>4同时8<9因此删去7添入8,数组——9,8; 输入——6,由于6<8直接添入,数组——9,8,6。
总的来说,它的本质就是当你在插入一个值时,应将在他之前存入的所有小于他的数值剔除,再将他存入数组中。
基础就差不多就这样ok辣~
良心推荐一些题:
单调队列专题:
1.P1440 求m区间内的最小值 直通
思路:
就是进行求解固定区间之内的最小值
上代码:
#include <iostream> #include <cstdio> #include <cstring> using namespace std; const int N = 1e6 + 5; int n,k,l,r; int a[N],q[N],p[N]; int main() { scanf("%d%d",&n,&k); for(int i=1; i<=n; i++) scanf("%d",&a[i]); for(int i=1; i<=n; i++) { if(q[l]<=i-k) l++; while(l<=r && a[i]<=a[q[r]]) r--; //单调递增 q[++r]=i; p[i]=a[q[l]]; } for(int i=k; i<=n; i++) printf("%d ",p[i]); printf("\n"); memset(p,0,sizeof(p)); l=0,r=0; for(int i=1; i<=n; i++) { if(q[l]<=i-k) l++; while(l<=r && a[i]>=a[q[r]]) r--; //单调递减 q[++r]=i; p[i]=a[q[l]]; } for(int i=k; i<=n; i++) printf("%d ",p[i]); return 0; }
2.luogu P1886 滑动窗口 直通
思路:
单调队列的裸题,练手题,必刷!
而且不仅有求最大值还有求最小值哦!
坑点:
看清楚输出的是什么!
上代码:
#include <iostream> #include <cstdio> #include <cstring> using namespace std; const int N = 1e6 + 5; int n,k,l,r; int a[N],q[N],p[N]; int main() { scanf("%d%d",&n,&k); for(int i=1; i<=n; i++) scanf("%d",&a[i]); for(int i=1; i<=n; i++) { if(q[l]<=i-k) l++; while(l<=r && a[i]<=a[q[r]]) r--; //单调递增 q[++r]=i; p[i]=a[q[l]]; } for(int i=k; i<=n; i++) printf("%d ",p[i]); printf("\n"); memset(p,0,sizeof(p)); l=0,r=0; for(int i=1; i<=n; i++) { if(q[l]<=i-k) l++; while(l<=r && a[i]>=a[q[r]]) r--; //单调递减 q[++r]=i; p[i]=a[q[l]]; } for(int i=k; i<=n; i++) printf("%d ",p[i]); return 0; }
3.luogu P1714 切蛋糕 直通
思路:
因为求的是幸运总和最大
总和?
当然就是求一下前缀和辣~
所以我们首先求一个前缀和,然后维护一个单调递增的前缀和,最后更新答案时则使用max(ans,sum[i]-sum[q[l]])就结束辣!
坑点:
注意这里ans的初始化是什么,我认为应该初始化为-99999999什么的,不过初始化为0也是可以的!
因为拥有ans=max(ans,sum[i]-sum[q[l]])这一步,所以如果当前的i==q[l]那么sum[i]-sum[q[l]]一定为0,所以无论你初始化ans多么小,ans最小都只能是0,所以需要把ans输出化为0.
上代码:
#include <iostream> #include <cstdio> #include <cmath> using namespace std; const int N = 500005; int n,m,ans,l,r; int sum[N],q[N]; int main() { scanf("%d%d",&n,&m); for(int i=1; i<=n; i++) { scanf("%d",&sum[i]); sum[i]+=sum[i-1]; if(q[l]<i-m) l++; while(l<=r && sum[q[r]]>sum[i]) r--; //维护一个单调增区间 q[++r]=i; ans=max(ans,sum[i]-sum[q[l]]); } printf("%d",ans); return 0; }
4.P1638 逛画展 直通
思路:
我们需要用桶来维护一下单调队列,记录一下每个画家的作品出现的次数,如果队头的元素跟后面的元素有重复,那么就删除掉这个元素,以此类推
然后最后的更新的话,必须保证所有颜色均出现过了,还需要记录一个last,表示上一次更新的区间的长度是多少。
如果现在的区间长度比last要小,并且所有颜色均出现,那么就更新a,b端点坐标以及last,然后继续重复即可
上代码:
#include <iostream> #include <cstdio> #include <algorithm> using namespace std; const int N = 1000001; const int M = 2001; int n,m,last=1<<30,a,b; int x[N],c[M]; int main() { scanf("%d%d",&n,&m); for(int r=1,l=1,now; r<=n; r++) { scanf("%d",&x[r]); if(c[x[r]]==0) m--; c[x[r]]++; while(l<r && c[x[l]]>1) { c[x[l]]--; l++; } now=r-l+1; if(m==0 && now<last) last=now,a=l,b=r; } printf("%d %d",a,b); return 0; }
额...至于单调栈什么的,我大概就做过一个题qwq,就光贴上地址吧
ok,先暂时说这么多,以后我如果做到类似的题目还会发啦~