单调队列学习笔记

为了学习单调队列优化dp去了解了前置知识。
什么是单调队列呢?
顾名思义,单调队列的重点分为 “单调” 和 “队列”
“单调” 指的是元素的的 “规律”——递增(或递减)
“队列” 指的是元素只能从队头和队尾进行操作
实际上维护一段变化区间里的单调的序列。

oiwiki中有下面的例子

原序列为

1 3 -1 -3 5 3 6 7

要求的是每连续的 k 个数中的最小值,
操作如下

操作队列状态2
1 入队{1}
3 比 1 大,3 入队{1 3}
-1 比队列中所有元素小,所以清空队列 -1 入队{-1}
-3 比队列中所有元素小,所以清空队列 -3 入队{-3}
5 比 -3 大,直接入队{-3 5}
3 比 5 小,5 出队,3 入队{-3 3}
-3 已经在窗体外,所以 -3 出队;6 比 3 大,6 入队{3 6}
7 比 6 大,7 入队{3 6 7}

总结下来就是:一旦遇到了破环队列单调性的值,就弹出之前队列中的数值,始终保持队列递增的状态.保证队首始终是当前区间里最小的值.

例题
滑动窗口
head tail维护单调队列中的队首和队尾 发现新元素
枚举i来遍历整个数列

#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 11;
int n,k;
int a[N];
int qx[N],qn[N];
void getmax(){//求最大值,保持队列单减,保证队首最大
    int head = 0,tail = 0;
    for(int i = 1; i <= k; i++){
        while(head <= tail && a[qx[tail]] <= a[i])   tail--;
        //找到前k个数中的最值 一旦遇到了破环队列单调性的值 就清空数列
        //遇到大的就不符合
        qx[++tail] = i;
    }
    for(int i = k; i <= n; i++){//同理
        while(head <= tail && a[qx[tail]] <= a[i])   tail--;
        qx[++tail] = i;
        while(qx[head] <= (i - k)){//因为题目中限制区间长度为k,队首位置超出区间也要出队
            head ++;
        }
        cout << a[qx[head]] << " ";
    }
}
void getmin(){
    int head = 0,tail = 0;
    for(int i = 1; i <= k; i++){
        while(head <= tail && a[qn[tail]] >= a[i])   tail--;
        qn[++tail] = i;
    }
    for(int i = k; i <= n; i++){
        while(head <= tail && a[qx[tail]] >= a[i])   tail--;
        qn[++tail] = i;
        while(qn[head] <= (i - k)){
            head ++;
        }
        cout << a[qn[head]] << " ";
    }
}
int main(){
    ios::sync_with_stdio(false);
    cin >> n >> k;
    for(int i = 1; i <= n; i++){
        cin >> a[i];
    }
    getmin();
    cout << endl;
    getmax();
    cout << endl;
    return 0;
}

另一道类似的题
洛谷P2698 [USACO12MAR]Flowerpot S

#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 11;
int n,d;
pair<int,int>a[N];
int q1[N];
int q2[N];
int h1,h2,t1,t2;
int main(){
    ios::sync_with_stdio(false);
    cin >> n >> d;
    for(int i = 1; i <= n ; i++){
        cin >> a[i].first >> a[i].second ;
    }
    sort(a + 1, a + 1 + n);
    h1 = h2 = 1;
    int l = 1,r = 0;
    int ans = 0x3f3f3f3f;
    for(l = 1; l <= n; l++){
        while(h1 <= t1 && q1[h1] < l)h1++;//维护区间 如果队首在左端点左边就右移
        while(h2 <= t2 && q2[h2] < l)h2++;
        while(a[q1[h1]].second - a[q2[h2]].second < d && r < n){
            r++;
            while(h1 <= t1 && a[q1[t1]].second < a[r].second)t1--;//注意是队尾和边界比较
            q1[++t1] = r;
            while(h2 <= t2 && a[q2[t2]].second > a[r].second)t2--;
            q2[++t2] = r;
        }
        if(a[q1[h1]].second - a[q2[h2]].second >= d){
            ans = min(ans,a[r].first - a[l].first);
        }
    }
    if(ans < 0x3f3f3f3f)cout << ans << endl;
    else puts("-1");
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值