zoukankan      html  css  js  c++  java
  • [洛谷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("
    ");
        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("
    ");
        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;
    }
  • 相关阅读:
    jvm内存配置参数
    6 个设计原则分别是什么?每种设计原则体现的设计模式是哪个?
    classloader 结构,是否可以自己定义一个 java.lang.String 类,为什么? 双亲代理机制。
    求sum=1+111+1111+........+1....111 .
    交换排序
    字符串压缩 stringZip
    TCP为何采用三次握手来建立连接,若采用二次握手可以吗
    使用jsoup抓取新闻信息
    通过地址获得经纬度(百度Geocoding API)
    FileReader
  • 原文地址:https://www.cnblogs.com/lyp-Bird/p/10628732.html
Copyright © 2011-2022 走看看