單調隊列,單調棧相關
說起這個話題,應該很多人會有一種似有所悟,但又不敢確定的感覺。
(我差不多就是那樣)
沒錯,這正是因為其中“單調”一詞的存在。
- 那麽單調是什麽?
- 學過函數的人都知道單調函數或者函數的單調性吧
- 其實直白一點說單調,就是一直增或一直減。
- 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; }View Code
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; }View Code
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; }View Code
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; }View Code
額...至於單調棧什麽的,我大概就做過一個題qwq,就光貼上地址吧
直通
ok,先暫時說這麽多,以後我如果做到類似的題目還會發啦~
單調隊列,單調棧相關