轉載http://blog.csdn.net/brodrinkwater/article/details/59713954
單調隊列
今天問了長者有關單調隊列的知識,單調隊列這種東西其實用途並不是特別的廣泛,只是在處理區間上詢問的時候比較管用,而且這種詢問有限制,比如:
一個含有n項的數列(n<=2000000),求出每一項前的m個數到它這個區間內的最小值。若前面的數不足m項則從第1個數開始,若前面沒有數則輸出0。
這種類似的題目,每次詢問前m個數中最小的,顯然暴力的話是O(n * m)的每個點枚舉一遍m,就是這樣,但是暴力顯然會TLE,那有什麼辦法呢?
RMQ是可以的,但是有一個問題,就是MLE,2000000 * 20,會炸空間,線段樹顯然是可以的,但是還不是最優的。所以我們引出單調隊列這種東西,解決序列上滑塊的問題(m這個區間在序列上動來動去,就像是個滑塊^_^).
單調隊列?
我們製作一個嚴格上升或下降的隊列,它的隊首是你處理當前區間的最大(最小)值,然後進行刪除和加入的操作。
加入: 我們假設有8,1,2,3,7,9,6.這樣的一個序列,我們想要一個單調上升的序列,這樣就可以保證隊首是最小的。我們加入元素的時候我們要存下每個值在原序列中的下標,因爲,我們只有處理[i-m,i]這樣的一個區間,如果隊首元素的下標不在這個區間,顯然就毫無意義了不。好,我們開始加入元素,我們每次添加元素,從隊尾開始比較,直到找到比它小的數替換掉。
假設沒有刪除操作
第一次 :8
第二次:1(1 < 8 所以 我們替換掉8)
第三次:1,2.
第四次:1,2,3.
第五次:1,2,3,7.
第六次:1,2,3,7,9.
第七次:1,2,3,6.(跟7,9,比較都小,所以替換,尾指針直到6的位置)。
刪除
刪除其實很簡單,我們比較隊首的下標如果不在範圍內直接刪掉。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
using namespace std;
#define REP(i,a,b) for(register int i = (a), i##_end_ = (b); i <= i##_end_ ; ++i)
inline int read()
{
char c = getchar();register int fg = 1,sum = 0;
while(c < '0' || c > '9')
{
if(c == '-')fg = -1;
c = getchar();
}
while(c >= '0' && c <= '9')
{
sum =sum * 10 + c - '0';
c = getchar();
}
return fg * sum;
}
const int maxn = 2000000+10;
struct T
{
int val,pos;
};
struct que
{
int l,r;
T a[maxn];
que(){l = 1, r = 0;}
inline void putin(T x)//加入
{
while(r >= l && a[r].val > x.val)r--;
a[++r] = x;
}
inline void pop(int pos)//刪除
{
while(r >= l && a[l].pos < pos)l++;
}
void print()
{
for(int i=l;i<=r;i++)
printf("%d ",a[i].val);
printf("\n");
}
inline int top()
{return a[l].val;}
}q;
int n,m,f[maxn];
int main()
{
n = read(),m = read();
REP(i,1,n)f[i] = read();
puts("0");
q.putin((T){f[1],1});
REP(i,2,n)
{ q.print();
if(i-m>=0)q.pop(i-m);
printf("%d\n",q.top());
q.putin((T){f[i],i});
}
return 0;
}