【模板】滑动窗口最值(单调队列)/洛谷P1886

题目链接

https://www.luogu.com.cn/problem/P1886

题目大意

有一个长为 \(n\) 的序列 \(a\) ,以及一个大小为 \(k\) 的窗口。现在这个从左边开始向右滑动,每次滑动一个单位,求出每次滑动后窗口中的最大值和最小值。

题目解析

使用 \(C++\)\(STL\) 库中的 \(deque\)
以窗口最小值为例,设该双端队列为 \(Q\) ,其队头则为每一次的答案:
首先,保证 \(Q\) 中均为未过期的值(即, \(Q\) 中元素均在窗口中)。
然后,比较 \(Q\) 的队头与新加入的值 \(a[i]\)

  • \(a[i]\) 更小,则可以清空队列,加入 \(a[i]\) (因为此时 \(a[i]\) 不但最新,而且最小)。
  • 若原队头更小,将 \(a[i]\) 加入队尾,但是在加入之前,确保 \(a[i]\) 大于队尾(如果 \(a[i] \leq\) 队尾,则明显更优,队尾就废弃直接弹出了)。

这样,就可以保证答案的正确性了。
其次,我们来考虑复杂度,队列中长度总不大于窗口大小,每一个元素只进出一次, \(a[i]\) 总比较次数不超过 \(n\) 次,因此总的时间复杂度为 \(O(n)\)
最大值同理。

参考代码

#include <bits/stdc++.h>
using namespace std;
deque <pair <int, int> > Q;
vector <int> a;

int main()
{
    int n, k, x;
    scanf("%d%d", &n, &k);
    for (int i = 0; i < n; ++i) {
        scanf("%d", &x);
        a.push_back(x);
    }
    /*求滑动窗口最小值*/
    for (int i = 0; i < n; ++i) {
        if (!Q.empty()) {
            if (a[i] <= Q.front().first) {Q.clear(); Q.emplace_front(a[i], i);}
            else {
                while (a[i] <= Q.back().first) Q.pop_back();
                Q.emplace_back(a[i], i);
            }
        }
        else Q.emplace_back(a[i], i);
        if (i >= k-1) {
            while (!Q.empty() && Q.front().second < i-k+1) Q.pop_front();
            if (!Q.empty()) printf("%d ", Q.front().first);
        }
    }
    putchar('\n');
    Q.clear();
    /*求滑动窗口最大值*/
    for (int i = 0; i < n; ++i) {
        if (!Q.empty()) {
            if (a[i] >= Q.front().first) {Q.clear(); Q.emplace_front(a[i], i);}
            else {
                while (a[i] >= Q.back().first) Q.pop_back();
                Q.emplace_back(a[i], i);
            }
        }
        else Q.emplace_back(a[i], i);
        if (i >= k-1) {
            while (!Q.empty() && Q.front().second < i-k+1) Q.pop_front();
            if (!Q.empty()) printf("%d ", Q.front().first);
        }
    }
    putchar('\n');
    return 0;
}

感谢支持!

posted @ 2020-09-02 21:16  Chiron-zy  阅读(190)  评论(0编辑  收藏  举报