1. 程式人生 > 實用技巧 >單調佇列與單調棧

單調佇列與單調棧

單調佇列與單調棧

單調佇列

  • 經典的滑動視窗問題: 求一個長度為n的序列A中所有長度為m(m < n)的子區間的最大值。n <= 2e6。

線段樹等等容易tle,我們需要一個o(n)的演算法來解決這個問題。

思路:

  • 考慮每個視窗,其中必然至少有一個位置為最大值。

  • 如果有一個,在這個視窗中那麼這個最大值前方的所有數字對我們都是無益的(不僅小,還退出的早);同樣如果有一個以上的最大值,那麼我們選取最後一個最大值即可(前方的最大值們不僅可以被替代,還退出的早)。

  • 而對其後方的所有數字,考慮到以後在這個最大值退出視窗之後,它們擁有成為最大值的“潛力”,所以是需要考慮的。

  • 但同樣,在這後方的所有數字中,如果存在 \(A_i <= A_j\)

    \(i < j\) 那麼這個 \(A_i\) 是不需要考慮的(不僅小,還退出的早)。

  • 所以在每個視窗中,我們只需要記錄當前的最大值以及這個最大值後面的數,當且僅當這個數的後面沒有大於等於它的數。

可以看出,這個視窗是可以用一個雙向佇列來模擬的,每當後方加入一個數,都要從佇列尾部開始淘汰掉所有的小於它的數,保證佇列中每一個數的後面,在視窗範圍內沒有大於等於它的數。當隊首元素不在視窗範圍時,隊首元素出隊。這樣操作後,隊內的元素都是單調的,所以叫作單調佇列

參考題目:洛谷P1440 (稍有不同)

P1440陣列模擬佇列程式碼:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

int n, m; 
struct ab
{
	int l;	//position 
	int v;
} que[2000005];
int head = 1, tail = 1;
int main()
{
	scanf("%d%d", &n, &m);
	printf("0\n");
	for (int i = 1; i < n; ++i)
	{
		int num;
		scanf("%d", &num);
		while (tail != head && num <= que[tail - 1].v)
		{
			--tail;
		}
		que[tail].l = i;
		que[tail++].v = num;
		while (que[head].l <= i - m)
		{
			++head;
		}
		printf("%d\n", que[head].v);
	}
	scanf("%d", &n);	//吞掉最後一個數的輸入 
	return 0;
}

單調棧

瞭解單調佇列後,單調棧讓人顧名思義想到的就是棧內所有元素是單調的

解決問題:

  • 1.求一個序列中任意一個數的後方/前方的第一個比自己大/小的數的下標

  • 2.求一個序列中任意一個數的後方/前方連著有多少個比自己小的數

這兩個問題本質是一樣的,但是樸素演算法顯然是 \(O(n^2)\) 的,我們要實現時間上的降維就要使用單調棧。

  • 舉例來說,求一個序列中任意一個數的後方的第一個比自己大的數的下標,如果不存在則為0,序列長度n <= 3e6。
    • 一開始我們在棧底放入一個INF
    • 將序列從左到右依次入棧
    • 當棧頂元素比當前數小時,棧頂元素出棧同時標記該棧頂元素的對應答案為入棧元素的下標,如此直到棧頂元素大於等於入棧元素
    • 最終留在棧內的元素對應答案都為0

模板題:洛谷P5788

P5788陣列模擬棧程式碼

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

int n;
int top = 1;

struct ab
{
	int v;
	int l;
} sta[3000005];

int ans[3000005] = {0};

int main()
{
	scanf("%d", &n);
	int xx;
	scanf("%d", &xx);
	sta[top].l = 1;
	sta[top++].v = xx;
	for (int i = 2; i <= n; ++i)
	{
		scanf("%d", &xx);
		while (top != 1 && sta[top - 1].v < xx)
		{
			ans[sta[top - 1].l] = i;
			--top;
		}
		sta[top].l = i;
		sta[top++].v = xx;
	}
	while (top != 1)
	{
		ans[sta[top - 1].l] = 0;
		--top;
	}
	for (int i = 1; i < n; ++i)
	{
		printf("%d ", ans[i]);
	}
	printf("%d", ans[n]);
	return 0;
}