POJ 2823 (從經典滑動視窗最大值問題入門單調佇列)
阿新 • • 發佈:2018-12-24
題目連結
題目大意
輸入一個長度為n(n
求:f(k),f(k+1),…,f(n) g(k),g(k+1),…,g(n).
分析
本題算是單調佇列的經典入門題。因此拿來學習單調佇列。
如果單純對每一個i都去掃描前面k個數去求最值,那複雜度為o(n*k),肯定超時。
如果用線段樹或者樹狀陣列維護區間最值,那複雜度為o(nlogn)。
但還有更簡潔更高效的辦法,即單調佇列
單調佇列是一種特殊的佇列,它能在佇列兩端進行刪除操作,並始終維護佇列保持一種單調性。
以求視窗最小值為例,我們需要維護一個內部元素值遞減的佇列來模擬滑動視窗,使每一次查詢取隊首元素即為答案。如何維護呢?有以下兩種操作:
1.從隊尾插入元素:當有新元素需要入隊時,讓它與當前隊尾元素進行比較,若它小於等於當前隊尾元素(即破壞了原佇列的單調性),那麼刪除隊尾元素,並繼續比較隊尾與新元素,直到找到一個隊尾大於新元素時,將新元素插入到隊尾。被刪除的元素既比新元素大,又會比新元素先滑出視窗,因此肯定不會成為答案。這個操作不斷維護了佇列中的最值。
2.刪除隊首元素:由於序列中的元素當且僅當在滑動視窗時有效,因此,當隊首元素不在滑動視窗內時,就刪除隊首元素。這個操作維護了資料的臨時性。
元素是否過時與其下標有關,因此我佇列中儲存的是序列數對應的下標,這樣每當要入隊一個數,只需訪問隊首元素所存下標在序列中對應的數即可得到答案。
這樣做盡管插入元素時有時需刪除多個數,但每個數最多被刪除一次,因此時間複雜度為O(n).
程式碼
陣列模擬單調佇列
#include <iostream>
#include <cstdio>
#include <cstring>
#define MAXN 1000010
using namespace std;
int a[MAXN],Min[MAXN],Max[MAXN],Q[MAXN],n,k;
void get_min()
{
int head,tail,i;
memset(Q,0,sizeof(Q));
head=1;tail=1;
Q[tail]=1;
Min[1]=a[1];
for (i=2;i<=n;i++)
{
while ((head<=tail)&&(a[i]<a[Q[tail]]))//刪除隊尾元素
tail--; //插入隊尾元素
Q[++tail]=i;
while ((head<=tail)&&(Q[head]<i-k+1)) //刪除隊首過時元素
head++;
/*由於每一層迴圈只新增一個數,因此每次最多刪除一次隊首,
故可改成if語句*/
Min[i]=a[Q[head]]; //訪問答案
}
}
void get_max()
{
int head,tail,i;
memset(Q,sizeof(Q),0);
head=1;tail=1;
Q[tail]=1;
Max[1]=a[1];
for (i=2;i<=n;i++)
{
while ((head<=tail)&&(a[i]>a[Q[tail]]))
tail--;
Q[++tail]=i;
while ((head<=tail)&&(Q[head]<i-k+1))
head++;
Max[i]=a[Q[head]];
}
}
int main()
{
int i;
scanf("%d%d",&n,&k);
for (i=1;i<=n;i++)
scanf("%d",&a[i]);
get_min();
get_max();
for (i=k;i<n;i++)
printf("%d ",Min[i]);
printf("%d\n",Min[n]);
for (i=k;i<n;i++)
printf("%d ",Max[i]);
printf("%d\n",Max[n]);
return 0;
}
使用STL中的雙端佇列deque容器
#include <iostream>
#include <cstdio>
#include <cstring>
#include <deque>
#define MAXN 1000010
using namespace std;
int a[MAXN],Min[MAXN],Max[MAXN],Q[MAXN],n,k;
void Get_Min()
{
deque<int> Q;
int i;
Q.push_back(1);
Min[1]=a[1];
for (i=2;i<=n;i++)
{
while ((!Q.empty())&&(a[i]<a[Q.back()]))
Q.pop_back();
Q.push_back(i);
while ((!Q.empty())&&(Q.front()<i-k+1))
Q.pop_front();
Min[i]=a[Q.front()];
}
}
void Get_Max()
{
deque<int> Q;
int i;
Q.push_back(1);
Max[1]=a[1];
for (i=2;i<=n;i++)
{
while ((!Q.empty())&&(a[i]>a[Q.back()]))
Q.pop_back();
Q.push_back(i);
while ((!Q.empty())&&(Q.front()<i-k+1))
Q.pop_front();
Max[i]=a[Q.front()];
}
}
int main()
{
int i;
scanf("%d%d",&n,&k);
for (i=1;i<=n;i++)
scanf("%d",&a[i]);
Get_Min();
Get_Max();
for (i=k;i<n;i++)
printf("%d ",Min[i]);
printf("%d\n",Min[n]);
for (i=k;i<n;i++)
printf("%d ",Max[i]);
printf("%d\n",Max[n]);
return 0;
}