1. 程式人生 > >單調隊列——求m區間內的最小值

單調隊列——求m區間內的最小值

microsoft n) 入隊和出隊 mes %d 序列 數組 lan mic

單調隊列,顧名思義是指隊列內的元素是有序的,隊頭為當前的最大值(單調遞減隊列)或最小值(單調遞增序列),以單調遞減隊列為例來看隊列的入隊和出隊操作:

1、入隊:

如果當前元素要進隊,把當前元素和隊尾元素比較,如果當前元素小於隊尾元素,那麽當前元素直接進隊,如果當前元素大於隊尾元素,那麽隊尾出隊,將當前元素和新的隊尾再做比較,直到當前元素大於隊尾元素或者隊列為空。單調隊列只能在隊尾插入元素,隊尾和隊頭都可以刪除元素。

2、出隊:

出隊直接取隊頭即可,因為用單調隊列就是為了取最值,而隊頭就是最值。

例子:將數組a[] = {3, 5, 2, 8, 1, 4, 7}依次入隊,並保證隊列為一個 單調遞減 隊列。

  • 3入隊,隊列為空直接入隊,隊列元素為:3;
  • 5入隊,5和隊尾比較,5大於3,3出隊,隊為空,5入隊,隊列元素為:5;
  • 2入隊,2和隊尾比較,2小於5,直接入隊,隊列元素為:5,2;
  • 8入隊,8和隊尾比較,2出隊,8再和隊尾比較,5出隊,隊為空,8入隊,隊列元素為:8;
  • 1入隊,...,隊列元素為:8,1;
  • 4入隊,...,隊列元素為:8,4;
  • 7入隊,...,隊列元素為:8,7。
實例應用: 1、給定一個數組a[]和一個長度為k的滑動窗口,該窗口從最左端移到最右端,找出窗口在每個位置是的最大值。 例如:a[] = {7, 3, 2, 5, 6},k = 3。我們可以維護一個單調遞減隊列,這樣對於每個位置,當前隊列的隊頭就是最大值,只不過在入隊的時候要檢查一下隊頭的下標是否已經超出窗口的範圍,如果超出就刪除隊頭元素即可。為了方便寫代碼,單調隊列很多時候保存的是下標,而不是數值本身。
  • i = 0,初始隊列為空,7入隊,隊列為:{ 0(7) }, (0為下標,括號為下標對應的值) ,窗口區間為[0],最大值為隊頭a[0] = 7;
  • i = 1,隊頭下標沒有超出k, 3入隊,隊列為:{ 0(7), 1(3) },窗口區間為[0,1],最大值為隊頭a[0] = 7;
  • i = 2,隊頭下標沒有超出k,2入隊,隊列為:{ 0{7), 1(3), 2(2) },窗口區間為[0,1,2],最大值為隊頭a[0] = 7;
  • i = 3,隊頭下標為0超出k,刪除隊頭,5入隊,隊列為:{ 3(5) },窗口區間為[1,2,3],最大值為隊頭a[3] = 5;
  • i = 4,隊頭下標沒有超出k,6入隊,隊列為:{ 4(6) },窗口區間為[2, 3, 4],最大值為隊頭a[4] = 6;
  • 窗口繼續向右滑動,如果當前隊頭下標超出範圍就刪除隊頭,然後去隊頭,沒有超出範圍就直接取隊頭。
這樣整個算法就是O(n)的。 洛谷P1440 求m區間內的最小值
#include<iostream>
#include<cstdio>
using namespace std;
int n,m,a[2000010];
int q[2000010],h=1,t=1;
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	scanf("%d",&a[i]);
	printf("0\n");
	q[1]=1;
	for(int i=2;i<=n;i++)
	{
		printf("%d\n",a[q[h]]);
		if(q[h]<=i-m)
		h++;
		while(a[i]<=a[q[t]]&&t>=h)
		t--;
		t++;
		q[t]=i;
	}
	return 0;
}

單調隊列——求m區間內的最小值