monotonic queue 單調隊列

index > Data Structures > monotonic queue


引子

POJ 2823 - Sliding Window,對一個長度爲nn的數組,求每個長度爲k(k<=n)k (k<=n)的區間的最小值和最大值。

暴力for可以想到一個O(NK)O(NK)的做法,當然,這篇文章是講O(N)O(N)的做法。

隊列

設定一個雙端隊列元素是{a[index],index}\{ a[index],index\} ,約束每次取到數組第ii個元素的時候有這樣幾種情況:

  • 當前元素比隊尾元素值小,則不斷彈出隊尾,再把此元素插入。
  • 當前元素比隊尾元素值大,則直接把此元素插入。

但再這個問題中還有個限制是kk區間,那麼每次插入前我們還需要把隊首元素做一次檢查,如果與當前下標距離大於kk 則不斷彈出隊首。

至此,當前元素處理完畢後,隊列的隊首,就是以當前位置爲右邊界的區間的最小值。

反向思維一下就可以做最大值了。

其他

優化空間

其實很好想到,你有數組了,存下標就夠了。

換個問法

除了O(N)O(N)遍歷定長子區間的最值,其實也可以反過來遍歷得到最值區間長

比如問一個數組裏,區間最小值不小於x的最大長度是多少。

這時候我們需要的關鍵信息變成了下標,隊首出隊條件變成了與x的比較。

第二個理題,牛客多校的那個題就是類似這樣的問法,不過更復雜,還要考慮差值。

注意

  • STL的deque在實際情況下還是會比較慢,建議用數組模擬一個。

  • 單調隊列很靈活,常常是作爲題目做法的某一步。

例題

POJ 2823 - Sliding Window

int Kase, n, m;

int a[MAXN];
int ans1[MAXN], ans2[MAXN];
deque<int> rMAX, rMIN;

int main() {
    ios_base::sync_with_stdio(0);
    cin.tie(0);

    cin >> n >> m;

    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }

    for (int i = 1; i <= n; i++) {
        while (!rMIN.empty() && i - rMIN.front() >= m)
            rMIN.pop_front();
        while (!rMAX.empty() && i - rMAX.front() >= m)
            rMAX.pop_front();

        while (!rMIN.empty() && a[rMIN.back()] > a[i])
            rMIN.pop_back();

        while (!rMAX.empty() && a[rMAX.back()] < a[i])
            rMAX.pop_back();

        if (rMIN.empty() || a[rMIN.back()] <= a[i])
            rMIN.push_back(i);

        if (rMAX.empty() || a[rMAX.back()] >= a[i])
            rMAX.push_back(i);

        if (i >= m) {
            ans1[i] = a[rMIN.front()];
            ans2[i] = a[rMAX.front()];
        }
    }
    for (int j = m; j <= n; ++j) {
        cout << ans1[j] << " \n"[j == n];
    }
    for (int j = m; j <= n; ++j) {
        cout << ans2[j] << " \n"[j == n];
    }
    return 0;
}

牛客883F - Planting Trees 單調隊列雙指針

參考

Minimum Stack / Minimum Queue

OI Wiki - 單調隊列

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章