單調隊列入門


本篇博客轉自我很久以前在洛谷上寫的一篇博客,原地址:https://www.luogu.org/blog/ybwowen/dan-diao-dui-lie

單調隊列是一種隊列(廢話

其中隊列的元素保證是單調遞增或者是單調遞減

那么隊首的元素不就是最小(或最大)的嗎?

我們結合具體的題目來看看吧:

傳送門:P1886 滑動窗口

題目描述

現在有一堆數字共N個數字(N<=10^6),以及一個大小為k

的窗口。現在這個從左邊開始向右滑動,每次滑動一個單

位,求出每次滑動后窗口中的最大值和最小值。

例如:

The array is [1 3 -1 -3 5 3 6 7], and k = 3.

輸入格式:

輸入一共有兩行,第一行為n,k。

第二行為n個數(<INT_MAX).

輸出格式:

輸出共兩行,第一行為每次窗口滑動的最小值

第二行為每次窗口滑動的最大值

輸入輸出樣例

輸入樣例#1:

8 3

1 3 -1 -3 5 3 6 7

輸出樣例#1:

-1 -3 -3 -3 3 3

3 3 5 5 6 7

這里我們只討論最大值,最小值原理一樣的

解法1:

如果按照常規方法,我們在求a[i] 即i~i+k-1區間內的最值

時,要把區間內的所有數都訪問一遍,時間復雜度約為

\(O(nk)\)。有沒有一個快一點的算法呢?

解法2:

很明顯,當我們在計算區間\([i-k+1,i]\)的最大值時,是不

是區間\([i-k+1,i-1]\)我們之前已經計算過了?那么我們

是不是要保存上一次的結果呢(主要是最大值)?

這時,單調隊列登場——

單調隊列主要有兩個操作:刪頭去尾

**1.刪頭 **

如果隊列頭的元素離開了我們當前操作的區間,那么這

個元素就沒有任何用了,我們就要把它刪掉

2.去尾

假設即將進入隊列的元素為\(X\),隊列尾指針為\(tail\)

這時我們要比較二者的大小:

1* \(X<q[tail]\)

此時q仍然保證着遞減性,故直接將\(X\)插入隊列尾

2*\(X>=q[tail]\)

此時,隊列遞減性被打破,此時我們做一下操作:

① 彈出隊尾元素

因為當前的\(q[tail]\)不但不是最大值,對於以后的情況

也不如\(X\)更優,所以要彈出

這好比當前隊列尾部的元素是個能力不足的老兵,而新加入

的新兵更年輕,更能打,當然不要老兵了

②重復執行①,直到滿足\(X<q[tail]\)或者隊列為空為止

③將\(X\)插入隊列

對於樣例而言:

[1 3 -1] -3 5 3 6 7 
q={1},{3},{3,-1} output:3//分別為每次操作的結果
1 [3 -1 -3] 5 3 6 7 
q={3,-1,-3} output:3
1 3 [-1 -3 5] 3 6 7
q={-1,-3},{-1,5},{5} output:5
1 3 -1 [-3 5 3] 6 7
q={5,3} output:5
1 3 -1 -3 [5 3 6] 7	
q={5,6},{6} output:6
1 3 -1 -3 5 [3 6 7]
q={6} output:7

由於每個元素最多入隊一次,出隊一次(為什么?),所以

時間復雜度為\(O(n)\)

當然,這題還可以用ST表和線段樹來做,但都沒有單調隊列

方便

實現:

由於要對隊首和隊尾進行維護,所以我們需要使用

雙端隊列

可以用STL中的deque,也可以手寫

代碼://使用deque

#include<bits/stdc++.h>
using namespace std;
int n,m;
int read(){//快讀 
	char ch=getchar();
	int f=1; int sum=0;
	while(ch<'0'||ch>'9'){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9') sum=sum*10+ch-'0',ch=getchar();
	return f*sum;
}
deque<int>q;
int a[1000005];
int main(){
	n=read(); m=read();
	for(int i=1;i<=n;i++) a[i]=read();
	for(int i=1;i<=n;i++){//最小值 
		while(!q.empty()&&a[q.back()]>a[i]) q.pop_back();//去尾 
		q.push_back(i);
		if(i>=m){
			while(!q.empty()&&q.front()<=i-m) q.pop_front();//刪頭 
			printf("%d ",a[q.front()]);
		}
	}
	printf("\n");
	while(!q.empty()) q.pop_front();
	for(int i=1;i<=n;i++){//最大值 
		while(!q.empty()&&a[q.back()]<a[i]) q.pop_back();//去尾 
		q.push_back(i);
		if(i>=m){
			while(!q.empty()&&q.front()<=i-m) q.pop_front();//刪頭 
			printf("%d ",a[q.front()]);
		}
	}
	printf("\n");
	return 0;
}

應用

單調隊列是一種小的數據結構,一半不單獨出現,

但是經常我們會遇到單調隊列優化的DP

舉例:NOIP 2017 PJ 第四題 P3957跳房子

二分+DP+單調隊列優化,這里請大家仔細查閱題解

某年NOIP TG 的初賽完善程序 烽火傳遞

雖然這題你可以用二叉堆,但明顯單調隊列更好

大家可以看看,這里我就不講這么多了

感謝閱讀!


免責聲明!

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



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