1. 程式人生 > >【題解】洛谷P1886 滑動視窗(單調佇列)

【題解】洛谷P1886 滑動視窗(單調佇列)

(之前從未聽說過這道題目 來到qbxt後大佬們都早就切掉此題了 倍感慚愧qwq)

題目大意就是給定一個序列A與要求的長度k,讓我們輸出A中所有長度為k的區間的最大值和最小值。

看到資料範圍後我們發現暴力會炸掉,所以要考慮比較簡潔的方法。這裡我們維護一個元素單調遞減的佇列求區間內的最大值,最後單調佇列隊首的元素一定是最大值。進一步地,插入了第i個元素後的單調佇列隊首元素一定是前i個元素的最大值,這就意味著單調佇列可以求出區間右端點向右移動的區間最值,我們再考慮如何讓區間左端點右移。

有兩個比較顯而易見但是很有用的結論:

1、單調佇列中的元素不僅值單調,在原序列中的位置也單調。

2

原序列中在單調佇列中相鄰的兩個元素之間的所有元素的值都比這兩個元素小。

那麼方法就有了:當隊首的元素不在當前處理的區間內時,刪去隊首的元素,重複判斷,直到隊首的元素在當前的區間內為止。

於是我們得到了一個線性的演算法。

PS:注意第一個點是可以直接放進去的,所以我們讓head=1,tail=0,初始化單調佇列。

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdlib>
using namespace std;
const int maxn=1000010;
int a[maxn];
int p[maxn],num[maxn]; //priority_queue & number
int head=1,tail=0;
int n,k;
void found_minn()
{
	head=1;
	tail=0;
	memset(p,0,sizeof(p));
	memset(num,0,sizeof(num));
	for(int i=1;i<=n;i++)
	{
		while(head<=tail&&p[tail]>=a[i])
		{
			tail--;
		}
		p[++tail]=a[i];
		num[tail]=i;
		while(num[head]<=i-k)
		{
			head++;
		}
		if(i>=k) printf("%d ",p[head]);
	}
}
void found_maxx()
{
	head=1;
	tail=0;
	memset(p,0,sizeof(p));
	memset(num,0,sizeof(num));
	for(int i=1;i<=n;i++)
	{
		while(head<=tail&&p[tail]<=a[i])
		{
			tail--;
		}
		p[++tail]=a[i];
		num[tail]=i;
		while(num[head]<=i-k)
		{
			head++;
		}
		if(i>=k) printf("%d ",p[head]);
	}
}
int main()
{
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
	}
	found_minn();
	printf("\n");
	found_maxx();
	return 0;
}