滑动窗口

洛谷 P1886 滑动窗口
单调队列有两个性质
  • 队列中的元素其对应在原来的列表中的顺序必须是单调递增的。
  • 队列中元素的大小必须是单调递*(增/减/甚至是自定义也可以)

就拿样例来谈谈,设以最小的为标准。

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

下文中我们用q来表示单调队列,p来表示其所对应的在原列表里的序号。


  1. 由于此时队中没有一个元素,我们直接令1进队。此时,q={1},p={1}。
  2. 现在3面临着抉择。下面基于这样一个思想:假如把3放进去,如果后面2个数都比它大,那么3在其有生之年就有可能成为最小的。此时,q={1,3},p={1,2}
  3. 下面出现了-1。队尾元素3比-1大,那么意味着只要-1进队,那么3在其有生之年必定成为不了最小值,原因很明显:因为当下面3被框起来,那么-1也一定被框起来,所以3永远不能当最小值。所以,3从队尾出队。同理,1从队尾出队。最后-1进队,此时q={-1},p={3}
  4. 出现-3,同上面分析,-1>-3,-1从队尾出队,-3从队尾进队。q={-3},p={4}。
  5. 出现5,因为5>-3,同第二条分析,5在有生之年还是有希望的,所以5进队。此时,q={-3,5},p={4,5}
  6. 出现3。3先与队尾的5比较,3<5,按照第3条的分析,5从队尾出队。3再与-3比较,同第二条分析,3进队。此时,q={-3,3},p={4,6}
  7. 出现6。6与3比较,因为3<6,所以3不必出队。由于3以前元素都<3,所以不必再比较,6进队。因为-3此时已经在滑动窗口之外,所以-3从队首出队。此时,q={3,6},p={6,7}
  8. 出现7。队尾元素6小于7,7进队。此时,q={3,6,7},p={6,7,8}。

咳咳,以上引自洛谷题解区dalao hankeke

单调队列的舞台

由于单调队列的队头每次一定最小值,故查询为O(1)。
进队出队稍微复杂点:
进队时,将进队的元素为e,从队尾往前扫描,直到找到一个不大于e的元素d,将e放在d之后,舍弃e之后的所有元素;如果没有找到这样一个d,则将e放在队头(此时队列里只有这一个元素)。
出队时,将出队的元素为e,从队头向后扫描,直到找到一个元素f比e后进队,舍弃f之前所有的。(实际操作中,由于是按序逐个出队,所以每次只需要出队只需要比较队头)。
每个元素最多进队一次,出队一次,摊排分析下来仍然是 O(1)。

引自 单调队列_360百科

所以我就负责上代码啦


q1单调递增
每次放元素先把比它大的弹掉 出了窗口的弹掉
q2相反

//手写队列
#include <iostream>
#include <cstdio>
using namespace std;
int n,k;
int ans[1000005];
struct T
{
    int i;
    int val;
}tmp;
inline int read()
{
    char c;
    int x=0,f=1;
    do{
        c=getchar();
        if(c=='-')
            f=-1;
    }while(c<'0' || c>'9');
    do{
        x=x*10+c-'0';
        c=getchar();
    }while(c>='0' && c<='9');
    return f*x;
}
struct qwq
{
    int h,t;
    T q[1000005];
    inline void push_back(T x)
    {
        q[++t]=x;
    }
    inline void push_front(T x)
    {
        q[--h]=x;
    }
    inline void pop_back()
    {
        t--;
    }
    inline void pop_front()
    {
        h++;
    }
    inline T back()
    {
        return q[t];
    }
    inline T front()
    {
        return q[h];
    }
    inline bool empty()
    {
        return h>t;
    }
}q1,q2;
int main(){
    q1.h=1,q1.t=0;
    q2.h=1,q2.t=0;
    n=read(),k=read();
    for(int i=0;i<n;++i)
    {
        tmp.i=i;
        tmp.val=read();
        while(!q1.empty() && q1.back().val>=tmp.val)
            q1.pop_back();
        while(!q1.empty() && q1.front().i<=i-k)
            q1.pop_front();
        q1.push_back(tmp);
        while(!q2.empty() && q2.back().val<=tmp.val)
            q2.pop_back();
        while(!q2.empty() && q2.front().i<=i-k)
            q2.pop_front();
        q2.push_back(tmp);
        if(i>=k-1)
        {
            printf("%d%c",q1.front().val,i==n-1?'\n':' ');
            ans[i]=q2.front().val;
        }
    }
    for(int i=k-1;i<n;++i)
    {
        printf("%d%c",ans[i],i==n-1?'\n':' ');
    }
    return 0;
} 
//deque双向队列
#include <iostream>
#include <cstdio>
#include <queue>
using namespace std;
int n,k;
int ans[1000005];
struct T
{
    int i;
    int val;
}tmp;
inline int read()
{
    char c;
    int x=0,f=1;
    do{
        c=getchar();
        if(c=='-')
            f=-1;
    }while(c<'0' || c>'9');
    do{
        x=x*10+c-'0';
        c=getchar();
    }while(c>='0' && c<='9');
    return f*x;
}
deque <T> q1,q2;
int main(){
    n=read(),k=read();
    for(int i=0;i<n;++i)
    {
        tmp.i=i;
        tmp.val=read();
        while(!q1.empty() && q1.back().val>=tmp.val)
            q1.pop_back();
        while(!q1.empty() && q1.front().i<=i-k)
            q1.pop_front();
        q1.push_back(tmp);
        while(!q2.empty() && q2.back().val<=tmp.val)
            q2.pop_back();
        while(!q2.empty() && q2.front().i<=i-k)
            q2.pop_front();
        q2.push_back(tmp);
        if(i>=k-1)
        {
            printf("%d%c",q1.front().val,i==n-1?'\n':' ');
            ans[i]=q2.front().val;
        }
    }
    for(int i=k-1;i<n;++i)
    {
        printf("%d%c",ans[i],i==n-1?'\n':' ');
    }
    return 0;
} 

emmmm…
附deque用法一波 又是抄dalao的

  1. d[i]:返回d中下标为I的元素的引用。
  2. d.front():返回的一个元素的引用。
  3. d.back():返回最后一个元素的引用。
  4. d.pop_back():删除尾部的元素。不返回值。
  5. d.pop_front():删除头部元素。不返回值。
  6. d.push_back(e):在队尾添加一个元素e。
  7. d.push_front(e):在对头添加一个元素e。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值