[洛谷P1886]滑动窗口 (单调队列)(线段树)

---恢复内容开始---

这是很好的一道题

题目描述:

现在有一堆数字共N个数字(N<=10^6),以及一个大小为k的窗口。

现在这个从左边开始向右滑动,每次滑动一个单位,求出每次滑动后窗口中的最大值和最小值。

例如:

队列 [1 3 -1 -3 5 3 6 7]


窗口大小为3.

则如下图所示:

 

输入输出格式:

输入格式:


输入一共有两行,第一行为n,k。

 

第二行为n个数(<INT_MAX).

 

输出格式:

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

 

输入样例:

 

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

 

输出样例:

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

 

 

解决方案:

(一)st表

(二)线段树

这里用到了两个结构体,然后就是进行普通的线段树求最大最小,这里就不再赘述了q

第一个结构体是查询用的

第二个结构体就是线段树了,这里我用了一个构造函数;

其实这些操作只是为了加速我们的线段树过程(让它别T)

不过总体地实现还是相对比较优美(复杂)的q

Code:

 

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#define inf 2147483647
using namespace std;
int a[12345678],n,k;
struct search_tree
{
    int minn;
    int maxn;
}q;
struct Segtree
{
    int minv[12345678],maxv[12345678];
    void pushup(int rt)
    {
        maxv[rt] = max(maxv[rt<<1],maxv[rt<<1|1]);
        minv[rt] = min(minv[rt<<1],minv[rt<<1|1]);
    }
    void build(int rt,int l,int r)
    {
        if(l == r)
        {
            maxv[rt] = a[l];
            minv[rt] = a[l];
            return ;
        }
        int mid = (l + r)>>1;
        build(rt<<1,l,mid);
        build(rt<<1|1,mid+1,r);
        pushup(rt);
    }
    search_tree solve(int rt,int l,int r,int ll,int rr)  //ll rr 为待求量 
    {
        if(ll <= l && rr >= r)
        return (search_tree)
        {
            minv[rt],
            maxv[rt]
        };
        int mid = (l+r)>>1;
        int minn = inf , maxn = -inf;
        search_tree ans;
        if(ll <= mid)
        {
            ans = solve(rt<<1,l,mid,ll,rr);
            maxn = max(maxn,ans.maxn);
            minn = min(minn,ans.minn);
        }
        if(rr > mid)
        {
            ans = solve(rt<<1|1,mid+1,r,ll,rr);
            maxn = max(maxn,ans.maxn);
            minn = min(minn,ans.minn);
        }
        return (search_tree)
        {
            minn,
            maxn
        };
    }
}segtree;
int main()
{
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++)
    scanf("%d",&a[i]);
    segtree.build(1,1,n);
    for(int i=1;i<=n - k + 1;i++)
    {
        q = segtree.solve(1,1,n,i,i + k - 1);
        printf("%d ",q.minn);
        a[i] = q.maxn;
    }
    printf("\n");
    for(int i=1;i<=n-k+1;i++)
    printf("%d ",a[i]);
    return 0;
}

 

 

 

 

 

(三)单调队列

单调队列概念:

  1. 队列中的元素其对应在原来的列表中的顺序必须是单调递增的。

  2. 队列中元素的大小必须是单调递*(增/减/甚至是自定义也可以)

这保证了单调队列的双有序

但是单调队列有一个特殊的特点就是可以双向操作出队。

但是我们并不是把单调队列里的每一个数都要存一遍,我们只需要存储这些单调队列里有序环境中有序的数(即我们所要求的目的)

这个概念还是很抽象的q

不过从这个题来看还是可以有所帮助的q

并不是每一个数的记录都是有意义的;

我们只需要存储那些对于我们来说有意义的数值;

以此题求最小值为栗子:

若有ai和aj两个数,且满足i<j。

如果ai>aj,那么两个数都应该记录;

但是如果aiaj,那么当aj进入区间后,ai的记录就没有意义了。

我们假设每个数能作为区间最大值的次数(即它可以存在区间内的次数)为它的贡献,当aj进入区间以后,在区间中存在的时间一定比ai长,也就说明ai一定不会再做贡献了

我们确定没有贡献的点,便可以直接删去

Code:

 

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
#define MAXN 1008666 
using namespace std;
struct Node
{
    int v;
    int pos;
}node[MAXN << 1];
int n,a[MAXN << 1],h = 1,t,k;
int m;
int main()
{
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++)
    scanf("%d",&a[i]);
    for(int i=1;i<=n;i++)  //维护单调递减队列 
    {
        while(h <= t && node[h].pos + k <= i)
        h++;
        while(h <= t && node[t].v >= a[i])
        t--;
        node[++t].v = a[i];
        node[t].pos = i;
        if(i >= k)
        printf("%d ",node[h].v);
    }
    h = 1;
    t = 0;
    printf("\n");
    for(int i=1;i<=n;i++)  //维护单调递增队列 
    {
        while(h <= t && node[h].pos + k <= i)
        h++;
        while(h <= t && node[t].v <= a[i])
        t--;
        node[++t].v = a[i];
        node[t].pos = i;
        if(i >= k)
        printf("%d ",node[h].v);
    }
    return 0;
}

 

posted @ 2019-03-30 20:04  6954717  阅读(219)  评论(0编辑  收藏  举报