zoukankan      html  css  js  c++  java
  • [模板]单调队列与单调栈

    目录

    单调队列:滑动窗口(算是重要的板题了)

    单调栈:最大矩形面积

    总结


    首先给一道板题:滑动窗口(十分重要,基本后面的复杂题由此题思路进行优化)

    题目描述

    给你一个长度为N的数组,一个长为K的滑动的窗体从最左移至最右端,你只能见到窗口的K个数,每次窗体向右移动一位,如下图:

     你的任务是找出窗体在各位置时的最大值和最小值。

    输入

    第1行:2个整数N,K(K<=N<=1000000) 第2行:N个整数,表示数组的N个元素(<=2*10^9)

    输出

    第1行:滑动窗口从左向右移动每个位置的最小值,每个数之间用一个空格分开 第2行:滑动窗口从左向右移动每个位置的最大值,每个数之间用一个空格分开

    样例输入

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

    样例输出

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

    解题思路

    既然都是板题了,那么便考虑一下怎么用单调队列来解决吧。

    首先说明一下单调队列:

    单调队列顾名思义就是队列中的元素是单调的(即单调上升单调下降。具体维护哪种因题而异)。队列需双边进出,我们通常用数组进行模拟,想来也要好操作一些。这道题想了一下如果用STL的模板好像不能够进行操作。

    这道题我们是要两个单调队列,一个维护上升,一个维护下降。先说一下维护下降的做法吧(相信读者知道下降就知道怎么弄上升了)

    其实方法很简单,一个元素需要放进队列的时候我们就需要将它与队列中的元素进行比较。如果a[i]>此时队列的元素,那么这一个元素便出队。反之,将a[i]放进队列。对顶元素就是其最大值,但由于这道题还有一个区间限制,我们还需while判断一下对顶元素是否在这一个区间内,如果不在。对顶便要出去。所以我们这道题数组模拟的队列要用结构体来进行储存。可以在输入时尝

    试构造这一个单调队列,不断维护,每一次维护后,每一个区间的答案便出来了。

    #include<cstdio>
    #include<cmath>
    #include<cstring>
    #include<algorithm>
    #include<vector>
    #include<map>
    #include<iostream>
    #define M 1000005
    using namespace std;
    struct node {
        int x, id ;
        node (){};
        node (int X,int ID){
            x = X;
            id = ID;
        }
    };
    node mx[M] , mi[M];
    int k , n , a[M] , head1 = 1 , tail1 , head2 = 1 , tail2 , ans1[M] , ans2[M];
    int main(){
        scanf ("%d%d",&n , &k);
        for (int i = 1;i <= n; i ++ ){
            scanf ("%d",&a[i]);
            while (tail1 >= head1){//对要新装进队列的元素进行处理(维护递减)
                if (a[i] > mx[tail1].x){//不满足单调性,弹出队列中这一个元素
                    tail1 -- ;
                }
                else{
                    mx[++ tail1] = node (a[i],i);//找到了合适位置,放入队列
                    break;
                }
            }
            if (tail1 < head1)//新要加进的元素弹出来所有点,我们要特殊处理
                mx[++ tail1 ] = node (a[i],i);
            while  (mx[head1].id <= i - k){//判断队顶元素是否在这一个区间
                head1 ++ ;
            }
            while (tail2 >= head2 ){//对要新装进队列的元素进行处理(维护递增)
                if (a[i] <= mi[tail2].x){
                    tail2 -- ;
                }
                else{
                    mi[++ tail2] = node (a[i],i);
                    break;
                }
            }
            if (tail2 < head2)//新要加进的元素弹出来所有点,我们要特殊处理
                mi[++ tail2] = node (a[i],i);
            while (mi[head2].id <= i - k)//判断队顶元素是否在这一个区间
                head2 ++ ;
            ans1 [i] = mx[head1].x;//保存最大值答案
            ans2 [i] = mi[head2].x;//保存最小值答案
        }
        for (int i = k;i < n;i ++ ){
            printf("%d ",ans2[i]);
        }
        printf("%d
    ",ans2[n]);
        for (int i = k;i < n;i ++ ){
            printf("%d ",ans1[i]);
        }
        printf("%d
    ",ans1[n]);
    }

     

    再来一道有关单调栈的题吧。

    说实话单调栈没啥用,单调队列依旧能够做到。

    最大矩形面积

    不想复制了,截图吧

    解题思路

    用单调栈,单调栈跟单调队列其实是一个意思,只不过只有一边进出。

    我们思考一下如果i为子矩阵中高度最小的矩阵,那么i所能构造的最大子矩阵也就是i左边第一个比它矮到i右边第一个比它矮的距离再乘上i的高度,那么原题便转换成了max(f[i] * h[i])(f[i]表示i左边第一个比它矮到i右边第一个比它矮的距离,h[i]表示i点的高度。)那么最关键的就是求i的l与r了,这时候单调栈(队列)就派上用场了。

    首先确定思路,我们维护栈内元素呈递增,那么装进h[i]时,便需要在栈里面弹东西。最后找到适合的放置位置便可以装入栈退出了。于是在这样一种操作中就可以求出f[i]了,模拟一下可以知道,当i进栈时,便找到了l,当i出栈时,便找到了r。

    于是这道题就可以解决了。

    #include<cstdio>
    #include<cstring>
    #include<cmath>
    #include<iostream>
    #include<algorithm>
    #include<vector>
    #include<queue>
    #include<map>
    #include<stack>
    #define MAXN 100005
    using namespace std;
    struct node {
        int x,id;
        node (){};
        node(int X,int ID){
            x = X;
            id = ID;
        }
    };
    stack<node>Q;
    int n , sum[MAXN] , ans , l[MAXN] , r[MAXN];
    inline void read(int &x){
        int f = 1;
        x = 0;
        char s = getchar();
        while (s < '0'|| s > '9'){
            if (s == '-')
                f = -1;
            s = getchar ();
        }
        while (s >= '0' && s <= '9'){
            x = x * 10 + s - '0';
            s = getchar();
        }
        x *= f ;
    }
    int main(){
        scanf ("%d",&n);
        for (int i =  1;i <= n; i ++ ){
            scanf ("%d",&sum[i]);
        }
        for (int i = 1;i <= n + 1; i ++ ){
            while (!Q.empty()){
                node X = Q.top();
                if (X.x < sum[i]){
                    Q.push(node(sum[i],i));
                    l[i] = X.id + 1;
                    break;
                }
                else{
                        Q.pop();
                        r[X.id] = i - 1;
                        ans = max (ans,(r[X.id] - l[X.id] + 1) * sum [X.id]);
                }
            }
            if (Q.empty() && i != n + 1){
                Q.push(node(sum[i],i));
                l[i] = 1;
            }
        }
        printf ("%d",ans);
    }

    总结

    总体来说单调队列还是用的比较多,我们用数组模拟便可以很轻松地实现两种。单调队列其实是有关DP的优化的。因此除了模板,单调队列一般要与DP所结合。如何要能够想到用单调队列的思想来做呢。就需要找DP的玄机了,DP是由上一状态转到现在这个状态,我们有时可以进行拆分,发现一些定值,另一个变值肯定是最后要求个最大值(最小值)。一般来说这都是二维,第一维估计是定值,第二维一般都是变值,我们就可以通过在第一维这一个线性中,运用单调队列来进行优化了。这只是一般情况(毕竟我遇到的题尚少)。难点的就有一道NOI的瑰丽华尔兹了。

  • 相关阅读:
    Spring Boot简明教程之实现Web开发及常用参数获取方式分析
    SpringBoot 简明教程之项目属性配置(三):配置文件优先级及多配置切换
    史上最简单MySQL教程详解(进阶篇)之存储过程(二)
    史上最简单MySQL教程详解(进阶篇)之存储过程(一)
    文字从中间向两边延展
    字符串转化成驼峰命名
    统计字符串字符个数
    while求和(1到100)
    for循环实现乘阶
    递归遍历所有ul下的所有子节点
  • 原文地址:https://www.cnblogs.com/lover-fucker/p/13566705.html
Copyright © 2011-2022 走看看