【POJ 2823】Sliding Window 双(单)端(调)队列 学习笔记~~~

首先来说明一下为什么双端队列是单调的呢??

因为我们再利用双端队列的时候,将满足一个条件的元素不断向后插入,满足另一个条件的元素向前插入。这样我们在更新一些元素的时候,这有满足某种条件,保证后面队列的情况一定是从前面的状态更新过来而且一定会比前面的那个状态更满足条件,这样。我们就保证了双端队列的单调性。

我们利用单调队列,可以来维护一个区间的最值。
该怎样维护呢????

我们以 POJ 2823为例:
题意就是说,给我们一个长度为n的区间,有一个长度为k的滑块,我们每次移动滑块,求每次移动后滑块所在区间的最值。(n <= 10^6 k <= n)

不考虑数据范围,我们可以想到这样一种暴力:模拟每次移动,记录最大值和最小值。
这样的复杂度是n*k 最坏的情况下会达到n^2 显然是不可取的。
而我们利用单调队列的话 会在队首记录我们的答案 查询是O(1)的 只需要遍历一遍序列,这个时间是O(n)的。那么这样,整体的时间复杂度就是O(n)的。

那么我们就来看一下怎样利用单调队列在队首存储答案。
首先明确:单调减的序列队首存的是最大值,单调增序列队首存的是最小值。我们支持在队尾插入元素的操作和在两端删除元素的操作。

一、如何插入一个元素:
我们插入元素时一定要和队尾元素比较。设当前插入的元素为v。
1、单调减队列:如果队尾元素要小于等于v,那么我们就让尾指针前移,表示删去这个元素,直到存在队尾元素大于v或队列为空,我们就插入v,同时记录下标。
2、单调增队列:如果队尾元素要大于等于v,那么我们就让尾指针前移,表示删去这个元素,直到存在队尾元素小于v或队列为空,我们就插入v,同时记录下标。

二、删除元素:
我们在插入元素的过程中不难发现,我们已经实现了删除一端(即队尾)的操作。我们只需要在进行删除队首的操作即可。那么怎么删呢??
我们刚刚在插入元素的时候,已经处理好下标了,因为第一次插入的时候我们在第一个区间[1,k)中进行操作(保证询问次数)并且不对队首做处理,所以我们需要判断[k,n]的区间内,当出现某个元素的下标小于i-k+1,那么就说明它并不在我们当前的区间[i,k+i]中,这样就不用考虑它们了,我们将头指针后移知道满足条件,同时也完成了删除这些队首元素的过程。

好,这样我们就在对首存下了最值,当然两种情况要分开记录。

附 POJ2823 代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 2333333;
int n,k,v[maxn];
int num[maxn],bh[maxn],head,tail;
int Num[maxn],Bh[maxn],Head,Tail;
int read()
{
    int b = 1,a = 0;
    char c = getchar();
    while(c > '9' || c < '0')
    {
        if(c == '-') b = -1;
        c = getchar();
    } 
    while(c >= '0' && c <= '9')
    {
        a = a * 10 + c - '0';
        c = getchar();
    }
    return a * b;
}
int main()
{
    n = read();k = read();
    for(int i = 1;i <= n;i ++) v[i] = read();
    head = Head = 1;
    tail = Tail = 0;
    for(int i = 1;i < k;i ++)
    {
        while(Head <= Tail && Num[Tail] >= v[i]) Tail--;
        Tail++;
        Num[Tail] = v[i];
        Bh[Tail] = i;
    }
    for(int i = k;i <= n;i ++)
    {
        while(Head <= Tail && Num[Tail] >= v[i]) Tail --;
        Tail ++;
        Num[Tail] = v[i];
        Bh[Tail] = i;
        while(Bh[Head] <= i - k) Head ++;
        printf("%d ",Num[Head]);
    }
    printf("\n");
    for(int i = 1;i < k;i ++)
    {
        while(head <= tail && num[tail] <= v[i]) tail --;
        tail ++;
        num[tail] = v[i];
        bh[tail] = i;
    }
    for(int i = k;i <= n;i ++)
    {
        while(head <= tail && num[tail] <= v[i]) tail--;
        tail ++;
        num[tail] = v[i];
        bh[tail] = i;
        while(bh[head] <= i - k) head ++;
        printf("%d ",num[head]);
    }
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值