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

题目描述

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

例如:

输入格式

输入一共有两行,第一行有两个正整数 n,k  第二行 n 个整数,表示序列 a

输出格式

输出共两行,第一行为每次窗口滑动的最小值
第二行为每次窗口滑动的最大值

输入输出样例

输入 #1

8 3
1 3 -1 -3 5 3 6 7

输出 #1

-1 -3 -3 -3 3 3
3 3 5 5 6 7

说明/提示

【数据范围】
对于 50% 的数据,1≤n≤10^5 ;
对于 100% 的数据,1≤k≤10^6,ai∈[−2^31,2^31)。


一开始的想法很简单,每次都遍历一遍窗口,然而窗口有这么多个,理所当然的也就超时了,哪怕用上了O2吸氧照样还是超时一个点,虽然对于其他点来说快了很多

#include <bits/stdc++.h>
using namespace std;

int n,k,a[1000000],s;
queue<int>minn,maxx;

int main(){
    cin>>n>>k,k--;
    for(int i=0;i<n;i++){
        scanf("%d",&a[i]);
    }

    while (s+k<n){
        int minnEle=INT_MAX,maxxEle=INT_MIN;
        for(int i=s;i<=s+k;i++){
            minnEle=minnEle<a[i]?minnEle:a[i];
            maxxEle=maxxEle>a[i]?maxxEle:a[i];
        }
        minn.push(minnEle);
        maxx.push(maxxEle);

        s++;
    }

    while(!minn.empty()){
        cout<<minn.front()<<" ";
        minn.pop();
    }cout<<endl;
    while(!maxx.empty()){
        cout<<maxx.front()<<" ";
        maxx.pop();
    }
    return 0;
}

 

那么有没有一种办法能让遍历这么多次窗口,变成只遍历一次原序列a呢?

核心思想:对于一个窗口和接下来的窗口,他们需要的只是最小值,所以大的就不需要了。

维护一个单调队列和一个与之对应的下标队列。

单调队列:

一个一个加入数据

1.维护队列的单调性,这样输出队头或者队尾就是需要的最大值或者最小值

2.维护入队数值下标的合理性,保证下标范围在窗口范围内

3.如果下标在窗口范围内输出队列的最值。

AC代码

#include <bits/stdc++.h>
using namespace std;

int n,k,a[1000001];
int monotoQ[1000001],monotoSubscriptQ[1000001];

void entMin(){
    int head=1,tail=0;
    for(int i=0;i<n;i++){
        while(head<=tail&&monotoQ[tail]>a[i])tail--;
        monotoQ[++tail]=a[i];
        monotoSubscriptQ[tail]=i;
        while(monotoSubscriptQ[head]<i-k+1)head++;
        if(i>=k-1)cout<<monotoQ[head]<<" ";
    }
}

void entMax(){
    int head=1,tail=0;
    for(int i=0;i<n;i++){
        while(head<=tail&&monotoQ[tail]<a[i])tail--;
        monotoQ[++tail]=a[i];
        monotoSubscriptQ[tail]=i;
        while(monotoSubscriptQ[head]<i-k+1)head++;
        if(i>=k-1)cout<<monotoQ[head]<<" ";
    }
}

int main(){
    cin>>n>>k;
    for(int i=0;i<n;i++)
        scanf("%d",&a[i]);

    entMin();
    cout<<endl;
    entMax();

    return 0;
}

AC代码(STL版)

#include <bits/stdc++.h>
using namespace std;
const int MAXN=1e6+1;

int n,k,a[MAXN];
void entmax(){
    deque<pair<int,int>>q;
    for(int i=1;i<=n;i++){
        while(!q.empty()&&q.back().second<a[i])q.pop_back();
        q.push_back({i,a[i]});
        while(!q.empty()&&q.front().first<=i-k)q.pop_front();
        if(i>=k)cout<<q.front().second<<" ";
    }
}
void entmin(){
    //维护降序队列321
    deque<pair<int,int>>q;//下标,数据
    for(int i=1;i<=n;i++){
        //队列back值大于加入的值,那就不需要了
        while(!q.empty()&&q.back().second>a[i])q.pop_back();
        q.push_back({i,a[i]});
        //队列的下标超时了,那就不需要了,最早的是front
        while(!q.empty()&&q.front().first<=i-k)q.pop_front();
        //满足窗口范围
        if(i>=k)cout<<q.front().second<<" ";
    }
}

int main(){
    scanf("%d %d",&n,&k);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
    }

    entmin();
    cout<<endl;
    entmax();
    return 0;
}

可以发现STL还是慢了很多的。

维护队列而不是维护一个pair<int,int>下标+数据是因为单个值会发生跳值的现象。谁试谁知道

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值