滑動視窗(單調佇列)
阿新 • • 發佈:2021-06-25
題目連結:https://www.luogu.com.cn/problem/P1886
題目內容:有一個長為 \(n\)的序列 \(a\),以及一個大小為 \(k\) 的視窗。現在這個從左邊開始向右滑動,每次滑動一個單位,求出每次滑動後窗口中的最大值和最小值。
樸素演算法:
對於每一個\(a_{i}\),列舉它與前面的\(k-1\)個元素進而獲得最大最小值,時間複雜度為\(O(nk)\),不能滿足資料要求。
分析:掃描整個陣列,針對每一個元素去列舉整個視窗內的最大最小值實際上做了很多重複不必要的工作,因為每一次視窗向右滑動一個單位,只是將視窗的左端滑出一個元素,相鄰兩個視窗的公共部分可以利用的資訊被重複處理多次。
優化:單調佇列
由於最大最小值的求法是一樣的,我們只討論區間最大值。
用佇列\(q[]\)(可以用陣列實現)儲存所有可能在後續可能作為答案的元素值,\(pos[]\)陣列儲存雙端中的元素在原陣列所對應的下標。\(l,r\)分別表示\(pos\)陣列的左右兩個端。
對於每一個元素\(a_{i}\),如果當前\(pos[l]\)的值滿足:\(pos[l] >= i + 1 - k\),說明雙端佇列的左端還未滑出視窗。否則需要將\(l\)增加到滿足條件即\(pos[l]\)距離當前元素位置\(i\)不超過\(k\)。將\(a_{i}\)插入單調佇列中時,不斷地將其與\(q[pos[r]]\)
另外,由於每次要求的是區間內的最大值,所以直接取出\(q[pos[l]]\)就可以了。
性質:雙端佇列中的元素單調遞減,\(a_{i}\)所替換掉的那些小於等於自己的元素在後續中不可能作為答案輸出,因為如果它們中的某一個可以作為答案輸出,那麼\(a_{i}\)大於等於它們,且\(a_{i}\)必定落於視窗內,不會更差。
注意點:\(pos[]\)中的值未必是連續的。
滑動區間最大值的核心程式碼:
l = r = 1; q[1] = a[1], pos[1] = 1; for(int i = 2;i <= n; i++) { while(l <= r && pos[l] < i + 1 - k)l++;//將視窗調整至滿足要求 //將a[i]插到單調佇列中的對應位置 while(l <= r && a[i] >= q[pos[r]])r--; pos[++r] = i; q[pos[r]] = a[i]; //每次取出佇列左端即為區間最大值 if(i >= k)cout<<q[pos[l]]<<' '; }