[整理] 單調隊列題目整理


寫在前面

連着刷了 4 天 12 道單調隊列,整理一下這些題的解法,找一找關於單調隊列共性的東西。

Luogu 1725 琪露諾

 dp 方程想起來比較簡單,但是顯然暴力轉移會 T,考慮單調隊列。

因為題目是要用 dp[i-k] 來更新 dp[i] ,所以單調隊列在轉移的時候 push 的應該是 dp[i-l],而不是 dp[i]

也就是說,對於這種從前面來更新當前的轉移, push 的時候應該 push i-k (k 為一個常數) 的值,push 完之后再用隊頭更新當前的答案。

#include<cstdio> #include<cstring> #include<iostream>
#define N 200005

int dp[N]; int n,l,r; int val[N]; int q[N],hd,tail; signed main(){ scanf("%d%d%d",&n,&l,&r); if(l>r) l^=r^=l^=r; for(int i=0;i<=n;i++) scanf("%d",&val[i]); hd=tail=1; q[1]=0; for(int i=l;i<=n;i++){ while(hd<=tail&&i-q[hd]>r) hd++; // printf("i=%d,q[hd]=%d\n",i,q[hd]);
        while(hd<=tail&&dp[q[tail]]<=dp[i-l]) tail--; q[++tail]=i-l; dp[i]=dp[q[hd]]+val[i]; // printf("dp[i]=%d\n",dp[i]);
 } int maxn=0; for(int i=n-r+1;i<=n;i++) maxn=std::max(maxn,dp[i]); printf("%d\n",maxn); return 0; } 
View Code

Luogu 3088 擠奶牛

顯然要正反用兩遍單調隊列。

但是一開始的想法跟單調棧有點類似,就是在 push 元素的時候,如果要 push 的元素是隊尾元素高度的兩倍,那么就給隊尾的元素打一個標記,然后 tail--

但是這樣的做法會造成漏判,所以,我們要換一種判斷的方法。

其實也很簡單,就是把判斷從當前元素判斷隊中的元素變為隊中的元素判斷當前元素。

在循環中,合法性判斷完之后,如果隊頭的元素是當前元素高度的兩倍,那么把當前元素打一個標記,這樣就完美解決了漏判的問題。

#include<cstdio> #include<algorithm>
#define N 50005

int n,m; int l[N],r[N]; int q[N],hd,tail; struct Node{ int x,height; }node[N]; bool cmp(Node a,Node b){ return a.x<b.x; } signed main(){ scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%d%d",&node[i].x,&node[i].height); std::sort(node+1,node+1+n,cmp); hd=1;tail=0; for(int i=1;i<=n;i++){ while(hd<=tail&&node[i].height>=node[q[tail]].height) tail--; q[++tail]=i; while(hd<=tail&&node[i].x-node[q[hd]].x>m) hd++; if(node[q[hd]].height>=2*node[i].height) l[i]=1; } hd=1,tail=0; for(int i=n;i;i--){ while(hd<=tail&&node[i].height>=node[q[tail]].height) tail--; q[++tail]=i; while(hd<=tail&&node[q[hd]].x-node[i].x>m) hd++; if(node[q[hd]].height>=2*node[i].height) r[i]=1; } int ans=0; for(int i=1;i<=n;i++) if(l[i]&&r[i]) ans++; printf("%d",ans); return 0; }
View Code

Luogu 1714 切蛋糕

想做法還是花了一些時間的,但是想到后秒出代碼。

對於這個序列,我們求一遍前綴和,記錄數組為 qzh。

定義 f[i] 表示吃第 i 塊蛋糕以及 i 之前連續一段長度小於 m 的蛋糕的幸運值。

顯然 f[i]=max{qzh[i]-qzh[i-k]},1≤k≤m

那么我們可以維護一個前綴和單調上升的隊列啊,隊尾即為最小值

水題水題~

#include<cstdio> #include<iostream>
#define N 500005

int n,m; int maxn; int qzh[N]; int q[N],hd,tail; signed main(){ scanf("%d%d",&n,&m); for(int x,i=1;i<=n;i++) scanf("%d",&x),qzh[i]=qzh[i-1]+x; hd=1,tail=0; for(int i=1;i<=n;i++){ while(hd<=tail&&i-q[hd]>m) hd++; maxn=std::max(maxn,qzh[i]-qzh[q[hd]]); while(hd<=tail&&qzh[q[tail]]>=qzh[i]) tail--; q[++tail]=i; } printf("%d\n",maxn); return 0; }
View Code

Luogu 2629 好消息,壞消息

這題就稍微有些難了,主要是拆環成鏈沒有想到。

如果想到了這一步,那么還是比較簡單的。

還是先求出前綴和數組 qzh。

我們假設 k 滿足條件,那么對於 min{qzh[i]},i∈[k,k+n-1],一定有 qzh[i]-qzh[k-1]>0。

有了這個性質,我們維護一個單調上升的隊列就好了,隊頭即為當前前綴和的最小值。

#include<cstdio> #include<iostream>
#define N 2000005

int n,ans; int q[N],hd,tail; int val[N],qzh[N]; signed main(){ scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&val[i]),val[i+n]=val[i]; for(int i=1;i<=(n<<1);i++) qzh[i]=qzh[i-1]+val[i]; hd=0,tail=1; for(int i=1;i<=n;i++){ while(hd<=tail&&qzh[i]<qzh[q[tail]]) tail--; q[++tail]=i; } for(int k=1;k<=n;k++){ while(hd<=tail&&q[hd]<k) hd++; if(hd<=tail&&qzh[q[hd]]-qzh[k-1]>=0) ans++; while(hd<=tail&&qzh[q[tail]]>=qzh[k+n]) tail--; q[++tail]=k+n; } printf("%d\n",ans); return 0; }
View Code

五  Luogu 2422 良好的感覺

這也是想了半天啊。。。

要求一段區間內最小值乘上區間和最大

對於元素 i ,可以單調棧找出它左邊第一個比它小的元素的位置 l 和右邊第一個比它小的元素的位置 r。

那么我們形象地說,當前這個元素 i 統治了 [l+1,r-1] 這個區間 (l+1 是有可能等於 r-1 的),那么對於 [l+1,r-1] 這段區間貢獻的答案,顯然就是 val[i]*(qzh[r-1]-qzh[l])。

答案取個 max 就好了。

注意開 long long !

#include<cstdio> #include<iostream>
#define N 100005
#define int long long

int n; int val[N]; int qzh[N]; int l[N],r[N]; int stk[N],top; signed main(){ scanf("%lld",&n); for(int i=1;i<=n;i++) scanf("%lld",&val[i]),qzh[i]=qzh[i-1]+val[i]; for(int i=1;i<=n;i++){ while(top&&val[i]<val[stk[top]]) l[stk[top]]=i,top--; stk[++top]=i; } while(top) l[stk[top--]]=n+1; for(int i=n;i;i--){ while(top&&val[i]<val[stk[top]]) r[stk[top]]=i,top--; stk[++top]=i; } while(top) r[stk[top--]]=0; int ans=-1234567890; for(int i=1;i<=n;i++) ans=std::max(ans,(qzh[l[i]-1]-qzh[r[i]])*val[i]); printf("%lld\n",ans); return 0; }
View Code

SCOI2009 生日禮物

大意是求最短的一段區間,使其包含所有 m 種元素 ( m≤60 )

最開始先掃一遍,不動頭指針,只動尾指針,直到使 [1,tail] 這段區間里包含所有的種類。

然后開始嘗試動頭指針,直到不能動為止,那么當前的 [head,tail] 里包含了所有的種類。

接着從 tail+1 開始循環,如果能讓頭指針往右動,就盡可能讓它動,再將當前元素扔進隊列里,用 tail-head 更新答案就好。

這種求區間包含所有種類元素的方法還是要學一學的。

#include<cstdio> #include<iostream> #include<algorithm>
#define N 1000005
#define int long long

int n,m,ans; int cnt,tot; int exis[65]; int q[N],l=1,r; struct Node{ int x,idx; friend bool operator<(Node a,Node b){ return a.x<b.x; } }node[N]; void update(int x){ if(!exis[node[x].idx]) tot++; exis[node[x].idx]++; } bool check(int x){ if(exis[node[x].idx]>1) return 1; return 0; } void down(int x){ exis[node[x].idx]--; } signed main(){ scanf("%lld%lld",&n,&m); for(int T,i=1;i<=m;i++){ scanf("%lld",&T); for(int x,j=1;j<=T;j++){ scanf("%lld",&x); node[++cnt].x=x; node[cnt].idx=i; } } std::sort(node+1,node+1+cnt); while(tot<m&&r<cnt) q[++r]=r,update(r); while(l<=r&&check(l)) down(l),l++; ans=node[r].x-node[l].x; for(int i=r+1;i<=n;i++){ q[++r]=i; exis[node[i].idx]++; while(l<=r&&check(l)) down(l),l++; ans=std::min(ans,node[r].x-node[l].x); } printf("%lld\n",ans); return 0; }
View Code

Luogu 2698 花盆

大意是求一段最小的區間,使得落在這段區間上的水滴的最大時間差滿足要求

注意到答案是滿足單調性的,所以二分答案后處理

但是由於我太蠢,沒有想到單調隊列的解題方法,於是敲了一發 ST 表。。。

單調隊列其實就是兩個滑動窗口維護區間最大和最小就行了,為什么沒想到啊。。。

代碼只有 ST 表版本的就不貼了

POI2011 Temperature

大意是每個元素都有一個浮動區間,求最長連續的一段,滿足該段內元素可能單調不降。

最開始想着掃一遍出結果的想法是錯的就不說了。。

正解依然是單調隊列(廢話不然你寫進來干嘛)

觀察到滿足題意的一段一定滿足這樣一個性質,即一段中最大的左端點一定小於等於最后一個元素的右端點。

那么我們維護一個左端點單調不降的隊列就好了

這樣隊頭存的就是這一段區間中最大的左端點

如果隊頭比當前元素的右端點大,那么就 head++,直到滿足題意

然后用當前元素位置減去隊頭元素位置更新即可

往隊尾 push 元素是一樣的,如果當前的左端點大於隊尾的左端點,tail-- 就好了,因為假如有一個數列滿足當前元素,那么也一定滿足左端點更小的元素。

#include<cstdio> #include<iostream>
#define N 1000005

int n,ans; int l[N],r[N]; int q[N],hd=1,tail; signed main(){ scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d%d",&l[i],&r[i]); for(int i=1;i<=n;i++){ while(hd<=tail&&r[i]<l[q[hd]]) /*printf("i=%d,q[hd]=%d\n",i,q[hd]),*/hd++; if(hd<=tail) ans=std::max(ans,i-q[hd-1]); //printf("q[hd]=%d,i=%d\n",q[hd],i);
        while(hd<=tail&&l[i]>=l[q[tail]]) /*printf("i=%d,q[tail]=%d\n",i,q[tail]),*/tail--; q[++tail]=i; } printf("%d\n",ans); return 0; }
View Code

呼大概就這么多吧~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM