單調隊列/單調棧入門詳解+題目推薦


以前一直以為這兩個是很高級的東西,這段時間用到了才開始學,發現實際上非常簡單

下面我們以單調隊列為例進行講解,單調棧自行類比


顧名思義

單調隊列這個名字就指明了它的性質——單調性

我們來看一道例題——滑動窗口

題面在此不再贅述,大意就是有一個長度為\(n\)的數列,一個長度為\(k\)的窗口,輸出窗口位於每個位置下的下的最大最小值

嗯,題目很好理解,st表或者線段樹過的先別說話,我們來看看另一種方法

我們維護一個長度為k的隊列,使得隊列的開頭為答案,那么我們每次只需要輸出開頭就好了。這個想法很好,可是怎么實現呢?

最大值為例,既然我們想要保證隊列開頭為答案,那么我們就要保證每次更新使最大值一直放在隊列。那么如果存儲的最大值該彈出了怎么辦呢?我們只需要記錄下每個元素的位置,判斷是否在區間內即可。

隊頭彈出后,第二位就變成了隊頭,我們就要保證這個數現在是區間內最大。那么是不是說,我們需要將這個長度為\(k\)的區間中的每一個數都存起來呢?不是的,並不是每個數被記錄都有意義的。舉個例子,\(a_i\)\(a_j\)兩個數,i<j。如果\(a_i\)>\(a_j\),那么兩個數都應該記錄;但是如果\(a_i≤a_j\),那么當\(a_j\)進入區間后,\(a_i\)的記錄就沒有意義了。很好理解,我們假設每個數作為區間最大值的次數為它的貢獻,當\(a_j\)進入區間以后,在區間中存在的時間一定比\(a_i\)長,也就說明\(a_i\)一定不會再做貢獻了,我們確定沒有貢獻的點,便可以直接刪去,以維護單調隊列的單調性。

那么這道題也就已經做出來了,我們再來看看代碼

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cctype>
#define ll long long
#define gc() getchar()
#define maxn 1000005
using namespace std;

inline ll read(){
	ll a=0;int f=0;char p=gc();
	while(!isdigit(p)){f|=p=='-';p=gc();}
	while(isdigit(p)){a=(a<<3)+(a<<1)+(p^48);p=gc();}
	return f?-a:a;
}

struct ahaha{    //v表示數的大小,pos表示數的位置
	int v,pos;
}q[maxn];
int n,a[maxn],h=1,t,k;
int main(){
	n=read();k=read();
	for(int i=1;i<=n;++i)a[i]=read();
	for(int i=1;i<=n;++i){
		while(h<=t&&q[h].pos+k<=i)++h;    //維護區間長度,使隊列內的數都在區間內
		while(h<=t&&q[t].v>=a[i])--t;    //維護隊列單調性,上已證明
		q[++t].v=a[i];q[t].pos=i;      //將當前元素放入隊尾
		if(i>=k)printf("%d ",q[h].v);
	}
	h=1;t=0;
	printf("\n");
	for(int i=1;i<=n;++i){
		while(h<=t&&q[h].pos+k<=i)++h;
		while(h<=t&&q[t].v<=a[i])--t;
		q[++t].v=a[i];q[t].pos=i;
		if(i>=k)printf("%d ",q[h].v);
	}
	return 0;
}

看到這里,你應該已經對單調隊列有了一個初步的理解,下面推薦幾道入門題(順序與難度無關)

例題

掃描

逛畫展

琪露諾

切蛋糕

[USACO09MAR]向右看齊Look Up

Bad Hair Day

Feel Good

Largest Rectangle in a Histogram


免責聲明!

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



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