有关单调队列

想要理解算法,最有效的方式就是看例题了。

所以我们就看这道模板题 

P1886 滑动窗口 /【模板】单调队列

刚开始我还不知道单调队列是个什么东西,但是大概知道要用一个双向的队列来写,于是就想到了multiset+queue。

我们首先把数字一个一个放到队列和multiset中,当我们发现队列的size等于k时,就可以将multiset中的第一个数和最后一个分别放到两个个记录数组中,一个是最小值一个是最大值,

然后二分找到muliset中队列的第一个数删去,再把队列第一个数pop掉就ok了,时间复杂度大概是nlogn。

然而这样的时间复杂度并不太行,t掉两个点只有80分,下面是代码。

#include<iostream>
#include<set>
#include<queue>
using namespace std;
multiset<int>q;
queue<int> p;
int minn[1000005], maxx[1000005], a = 0;
int main() {
    int n, m; cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        int now; cin >> now;
        p.push(now);
        q.insert(now);
        if (p.size() == m) {
            a++;
            minn[a] = *q.begin();
            auto b = q.end(); b--;
            maxx[a] = *b;
            b = q.lower_bound(p.front()); q.erase(b);
            p.pop();
        }
    }
    for (int i = 1; i <= a; i++)
        cout << minn[i] << " ";
    cout << endl;
    for (int i = 1; i <= a; i++)
        cout << maxx[i] << " ";
    return 0;
}

之后没有办法,就只好去学习了一下单调队列。

单调队列跟优先队列很像,都是队列中的元素要么单调递增要么单调递减,但是单调队列可以对两头进行操作。

由于stl当中没有单调队列,我们就只能手打(胡说明明有deque)。

对于这题来说,其实就是要我们维护一个大小为k的两个单调队列,一个单调递增,一个单调递减。

对于单调递增队列来说,我们只要遇到一个比队列中最后一个数还要小的数,那么我们就一直从队尾出队直到这个数比队尾的数大,并将其加入队列中。

而单调递增则相反不必赘述,下面直接上代码就好了。

#include<iostream>
using namespace std;
int a[1000005], q1[1000000], q2[1000005], n, k,book[1000005];
void maxx() {
    int tail = 0;
    int head = 1;
    for (int i = 1; i <= n; i++) {
        while (head <= tail && a[i] >= q2[tail])tail--;
        q2[++tail] = a[i];
        book[tail] = i;
        while ( book[head] <= i - k)
            head++;
        if (i >= k)cout << q2[head] << " ";
    }
}
void minn() {//求窗口中最小的,其实是求一个单调递增队列,这样每次输出它的头部,那就必是最小的了
    int tail = 0;
    int head = 1;
    for (int i = 1; i <= n; i++) {
        while (head <=tail && a[i] <= q1[tail])tail--;//遇到比队尾小的,我们就一直出队,直到大于队尾为止
        q1[++tail] = a[i];
        book[tail] = i;
        while (book[head] <= i - k)
            head++;
        if (i >= k)cout << q1[head] << " ";
    }
    cout << endl;
}
int main() {
    cin >> n >> k;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    minn(); maxx();
    return 0;
}

我们这里采用了个手写队列。

手写队列时要将头节点初始化为1,尾节点初始化为0,具体为什么讲不清楚,但这样才能实现队列的操作。

posted @ 2021-02-09 22:32  redintonc  阅读(65)  评论(0)    收藏  举报