POJ 2823(单调队列)

Description

An array of size n ≤ 106 is given to you. There is a sliding window of size k which is moving from the very left of the array to the very right. You can only see the k numbers in the window. Each time the sliding window moves rightwards by one position. Following is an example: 
The array is [1 3 -1 -3 5 3 6 7], and k is 3.
Window positionMinimum valueMaximum value
[1  3  -1] -3  5  3  6  7 -13
 1 [3  -1  -3] 5  3  6  7 -33
 1  3 [-1  -3  5] 3  6  7 -35
 1  3  -1 [-3  5  3] 6  7 -35
 1  3  -1  -3 [5  3  6] 7 36
 1  3  -1  -3  5 [3  6  7]37

Your task is to determine the maximum and minimum values in the sliding window at each position. 


Input

The input consists of two lines. The first line contains two integers n and k which are the lengths of the array and the sliding window. There are n integers in the second line. 

Output

There are two lines in the output. The first line gives the minimum values in the window at each position, from left to right, respectively. The second line gives the maximum values. 

Sample Input

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


Sample Output

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


思路

如果考虑直接枚举 时间复杂度为O(n*k) 可能会超时

这里可以使用单调队列作为存储结构,由于“滑动窗口”的特殊性,可以O(n)的维护最大值或最小值

例如单调递减队列,是这么一个队列,它的头元素一直是队列当中的最大值,而且队列中的值是按照递减的顺序排列的。
可以从队列的末尾插入一个元素,从队列的两端删除元素。 为了保证队列的递减性,我们在插入元素v的时候,要将队尾的元素和v比较,如果队尾的元素不大于v,则删除队尾的元素,然后继续将新的队尾的元素与v比较,直到队尾的元素大于v,这个时候我们才将v插入到队尾。


对于本题,由于是窗口在滑动,队首也要维护。观察可知,当队尾为i时,队首位置为i-k+1。

所以当队首的元素的索引或下标小于i-k+1的时候,就说明队首的元素对于求f(i)已经没有意义了,因为它已经不在窗里面了。所以当index[队首元素]<i-k+1时,将队首元素删除。

求最小值同理,再弄一个递增的单调队列
时间复杂度:因为每个元素进队一次,出队一次,所以时间复杂度是O(n)的



代码示例

//#define LOCAL
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<stdlib.h>
#include<string.h>
#include<algorithm>
using namespace std;

const int maxn=1e6;
const int inf=0x3fffffff;

typedef struct
{
    int num[maxn+50],t[maxn+50];//num表示数字,t表示时间(这里是下标)
    int l,r;
    void init()
    {
        l=1;
        r=0;
    }
    void push(int nu,int tu)//插入一个数字nu,时间为tu的元素
    {
        while(r>=l&&num[r]>=nu) --r;
        ++r;
        num[r]=nu,t[r]=tu;
    }
    void pop(int tu)
    {
        while(l<=r&&t[l]<tu) ++l;
    }
    int getmin()
    {
        if(l>r) return inf;
        else return num[l];//左端为最小
    }
}Mono_queue1;

typedef struct
{
    int num[maxn+50],t[maxn+50];//num表示数字,t表示时间(这里是下标)
    int l,r;
    void init()
    {
        l=1;
        r=0;
    }
    void push(int nu,int tu)//插入一个数字nu,时间为tu的元素
    {
        while(r>=l&&num[r]<=nu) --r;
        ++r;
        num[r]=nu,t[r]=tu;
    }
    void pop(int tu)
    {
        while(l<=r&&t[l]<tu) ++l;
    }
    int getmax()
    {
        if(l>r) return inf;
        else return num[l];//左端为最大
    }
}Mono_queue2;

int arr[maxn];
Mono_queue1 a;
Mono_queue2 b;

int main()
{
    std::ios::sync_with_stdio(false);
    #ifdef LOCAL
        freopen("read.txt","r",stdin);
    #endif
    int n,k;
    while(cin>>n>>k)
    {
        for(int i=0;i<n;++i){
            cin>>arr[i];
        }
        a.init();
        for(int i=0;i<n;++i){
            a.push(arr[i],i);
            a.pop(i-k+1);
            if(i>k-1) cout<<' '<<a.getmin();
            if(i==k-1) cout<<a.getmin();
        }
        cout<<endl;
        b.init();
        for(int i=0;i<n;++i){
            b.push(arr[i],i);
            b.pop(i-k+1);
            if(i>k-1) cout<<' '<<b.getmax();
            if(i==k-1) cout<<b.getmax();
        }
        cout<<endl;
    }
    return 0;
}



思考

f[x]=max/min{g(k)}+w[x]是核心


由于单调队列的队头每次一定最小值,故查询为O(1)。


每个元素最多进队一次,出队一次,摊排分析下来仍然是 O(1)。




关于DP

动态规划时常常会见到形如这样的转移方程:
f[x] = max or min{g(k) | b[x] <= k < x} + w[x]
(其中b[x]随x单调不降,即b[1]<=b[2]<=b[3]<=...<=b[n])
(g[k]表示一个和k或f[k]有关的函数,w[x]表示一个和x有关的函数)
这个方程怎样求解呢?我们注意到这样一个性质:如果存在两个数j, k,使得j <= k,而且g(k) <= g(j),则决策j是毫无用处的。因为根据b[x]单调的特性,如果j可以作为合法决策,那么k一定可以作为合法决策,并且k是一个比j要优的决策。因为k比j要优,(注意:在这个经典模型中,“优”是绝对的,是与当前正在计算的状态无关的),所以,如果把待决策表中的决策按照k排序的话,则g(k)必然是不降的。在此例中决策表即f[x].
这样,就引导我们使用一个单调队列来维护决策表。对于每一个状态f(x)来说,计算过程分为以下几步:
1、 队首元素出队,直到队首元素在给定的范围中。
2、 此时,队首元素就是状态f(x)的最优决策,
3、 计算g(x),并将其插入到单调队列的尾部,同时维持队列的单调性(不断地出队,直到队列单调为止)。
重复上述步骤直到所有的函数值均被计算出来。不难看出这样的算法均摊时间复杂度是O(1)的。因此求解f(x)的时间复杂度从O(n^2)降到了O(n)。







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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值