單調佇列 優先佇列

2022-02-13 19:23:00 字數 2612 閱讀 6315

「如果乙個人比你年輕還比你強,那你就要被踢出去了……」——單調佇列

「來來來,神犇巨佬、金牌\(au\)爺、\(aker\)站在最上面,蒟蒻都靠下站!!!」——優先佇列

顧名思義,所謂單調佇列,那麼其中的元素從隊頭到隊尾一定要具有單調性(單調公升、單調降等)

它被廣泛地用於「滑動視窗」這一類\(rmq\)問題,其功能是\(o(n)\)維護整個序列中長度為\(k\)的區間最大值或最小值

給定乙個長度為\(n\)的序列\(a\)和乙個視窗長度\(k\),視窗初始覆蓋了\(1\rightarrow k\)這些元素

之後視窗每次向右移乙個單位,即從覆蓋\(1\rightarrow k\)變成覆蓋\(2\rightarrow k+1\)

要求求出每次移動(包括初始時)視窗所覆蓋的元素中的最大值(如圖,花括號內即為被視窗覆蓋的元素)

資料範圍:\(1\leq k\leq n\leq 10^6,a_i\in[-2^,2^)\)

「越接近暴力的資料結構,能維護的東西就越多」——真理

線段樹和樹狀陣列維護不了眾數,但分塊可以。你再看暴力,它什麼都能維護……

很簡單,每次從視窗最左端掃到最右端,然後取最大值就\(ok\)了

顯然在這種資料強度下暴力是過不了的,**就不給了

思考暴力為什麼慢了:因為視窗每次才移動\(1\)個單位,但是暴力演算法每次都重複統計了\(k-2\)個元素

那我們把中間那一大堆數的最大值記錄下來,每次進來乙個元素,出去乙個元素,統計一下最值,這不就快了嗎?

但是,不幸的是,如果出去的那個元素正好是最值,那就得重新統計了

考慮維護乙個單調不公升佇列,每次新元素進來之前,從這個佇列的最小值向最大值依次比較

如果這個佇列中的乙個數\(a\)沒有新來的那個元素\(b\)大,那麼把\(a\)踢出序列

因為\(a\)一定在新來的數之前出現,它的值沒有\(b\)大,所以在之後的統計中\(a\)永遠也不可能成為最大值,就沒必要記錄\(a\)了

處理完新元素,現在看看舊元素怎麼處理:

乙個數\(a\)如果不在視窗裡,那麼需要把它踢出這個佇列,但是如果我們每次移動都要找到這個\(a\)再踢出,那麼複雜度又變成了\(o(nk)\),顯然不行

發現新元素不受舊元素的影響,每次一定會進入到佇列裡,不會因為舊元素而把新元素卡掉,而且我們只是查詢最大值,所以沒有必要嚴格維護序列裡每個值都在視窗裡,只要保證最大值出自視窗裡即可

因為這個佇列單調不公升,所以隊頭一定是我們要查詢的最大值,那麼我們可以對隊頭掃瞄,如果這個隊頭在視窗之外,把這個隊頭踢出去,新的隊頭是原來的第二個元素

重複上述操作,直到隊頭在視窗裡即可,因為序列單調不公升,所以隊頭一定是視窗內的最大值

以上就是單調佇列演算法的全部內容

複雜度分析

有些剛學的同學,看到迴圈\(n\)重巢狀,馬上來一句:這個演算法的複雜度是\(o(n^n)\) 的,這是不對的!!!

比如剛才我們的這個演算法,看似每次視窗移動時都要對整個單調佇列進行掃瞄,但是,從總體來看,每個元素只會入隊一次,出隊一次,所以複雜度是\(o(n)\)的

核心\(code\)

struct node

}q[1e6+10];

int main()

}bool operator < (const node a,const node b)

這樣,系統就會認為小的更大,所以小的就會跑到堆頂去

但是,如你想要\(int\)型別的小根堆,千萬不要過載運算子,這樣普通的兩個\(int\)數就不能正常比較了(系統會認為小的更大)

常用操作命令

//std priority_queue 操作命令

q.push();//插入元素,複雜度o(logn)

q.pop();//彈出堆頂,複雜度o(logn)

q.size();//返回堆中元素個數,複雜度o(1)

q.top();//返回堆頂元素,複雜度o(1)

什麼?你想讓\(priority\)_\(queue\)支援隨機刪除,但是又不想手寫?(那你可真是懶

但是這能難倒人類智慧型嗎?顯然不能,這裡有乙個玄學的延遲刪除法,可以滿足需求

我們可以維護另乙個優先佇列(刪除堆),每次要刪除乙個數(假設為\(x\))

當需要刪除\(x\)的時候,我們並不去真正的堆裡面刪除\(x\),而是把\(x\)加入刪除堆

訪問維護最值的堆時,看看堆頂是不是和刪除堆堆頂一樣,如果一樣,說明這個數已經被刪掉了,在原堆和刪除堆中同時\(pop\)掉

這個方法為什麼對呢?萬一原堆的堆頂\(x\)已經被刪了,而刪除堆的堆頂不是\(x\),導致找到了錯的最值,怎麼辦呢?

其實這種情況不可能出現。假設我們維護了乙個大根堆,如果刪除堆的堆頂不是\(x\),那必然是乙個比\(x\)大的數\(y\)

如果\(y\)還沒有被刪除,那麼比\(y\)小的\(x\)一定還不是堆頂,幾次彈出後,堆頂是\(y\),發現刪除堆堆頂同樣是\(y\),\(y\)從原堆和刪除堆中刪除

換句話說,當原堆的堆頂是\(x\)時,刪除堆堆頂和原堆中還需要刪除的數一定\(\leq x\),所以不會找到錯誤的最值

單調佇列 優先佇列

dequeue int que 建立雙向佇列 que.push front 在佇列前面塞乙個元素 que.push back 在佇列後面塞乙個元素 que.pop front 刪除佇列第乙個元素 que.pop back 刪除佇列的最後乙個元素 que.clear 清空佇列 que.empty 判斷...

RMQ 優先佇列與單調佇列 專題訓練

指使用stl庫的priority queue進行模擬,優點在於實現簡單。可用於求區間最值,由於使用堆操作,時間複雜度在 n log 2 n 2 log 2 當資料較大時容易tle 單調佇列使用stl的deque進行模擬,也可以用陣列和雙指標 head,tail 有兩種操作,刪頭和去尾,實現乙個區間內...

棧和佇列 單調佇列 單調棧

講解部落格鏈結 一 單調棧 1 什麼是單調棧?單調棧是指乙個棧內部元素具有嚴格單調性 單調遞增,單調遞減 的一種資料結構。2 單調棧的兩個性質 滿足從棧頂到棧底具有嚴格的單調性 滿足後進先出的特徵,越靠近棧底的元素越早的進棧。3 元素進棧的過程 對於當前進棧元素x 如果x 棧頂元素,x 進棧。否則 ...