单调队列 POJ 2823+单调栈 HDU 1506

    单调队列就是用数组拉模拟队列的性质,保证队列里的元素是单调递增或单调递减,时间复杂度O(n)

    单调队列解决的是区间最小(最大)值。实现方法是:求区间最小(最大)值,就维护一个递增的双端队列,队中保存原始序列的标号,当即将入队的元素的值比队尾的元素的值小(大)的时候就不断弹掉队尾,直到出现比它更小的值,当即将入队的元素队首元素的跨度(即将入队元素的序号到队首元素序列的区间)大于规定区间时就不断弹掉队首,直到跨度小于或等于所规定的区间。如此可保证队首元素为最小(最大)值,(但不能保证队尾就是原始序列中的最大(最小)值),并维护区间长度。PS:借鉴别人的描述,大概就是这个道理

找区间最大最小值也能用RMQ,区间最值查询来写,但没有单调栈简单,复杂度低。

POJ 2823(单调队列):http://poj.org/problem?id=2823

eg:8 3(n个数,找区间范围为k的最大值和最小值)
    1 3 -1 -3 5 3 6 7
每次进出队列的是(数字,下标),下标是用来限制区间范围,删除队列有、头部无用的元素,下面来模拟这个过程
找一段区间的最小值,维护单调递增的队列
(1,1)
(1,1)(3,2)
(-1,3)
(-3,4)
(-3,4)(5,5)
(-3,4)(3,6)
(3,6)(6,7)//其中(-3,4)已经超过当前要找的区间范围了(7-3+1=5,当前要找的范围为【5,7】),属于无用元素,删除
(3,6)(5,7)(7,8)
找一段区间的最大值,原理是一样的,维护单调递减的队列,由于队列不能删除队尾元素,所以我们用数组来模拟的

自己可以手动模拟一边,这样才会更深的了解

CODE:

#include<stdio.h>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
#include<math.h>
#include<map>
#include<queue>
#include<stack>
#include<string.h>
typedef long long LL;
using namespace std;
#define INF 0x3f3f3f3f
const int maxn=1000005;
const double eps=0.0001;
struct node
{
    int x,pos;
} v[maxn];
int mx[maxn],mn[maxn],n,m,a[maxn];
void getmin()//维护单调递增的队列,找区间最小值
{
    int head=1,tail=0,i;
    for(i=1; i<m; i++)
    {
        while(head<=tail&&v[tail].x>a[i])
            tail--;
        v[++tail].x=a[i],v[tail].pos=i;
    }
    for(; i<=n; i++)
    {
        while(head<=tail&&v[tail].x>a[i])
            tail--;
        v[++tail].x=a[i],v[tail].pos=i;
        while(v[head].pos<i-m+1)
            head++;
        mn[i-m+1]=v[head].x;
    }
}
void getmax()//维护单调递减的队列,找区间最大值
{
    int head=1,tail=0,i;
    for(i=1; i<m; i++)
    {
        while(head<=tail&&v[tail].x<a[i])
            tail--;
        v[++tail].x=a[i],v[tail].pos=i;
    }
    for(; i<=n; i++)
    {
        while(head<=tail&&v[tail].x<a[i])
            tail--;
        v[++tail].x=a[i],v[tail].pos=i;
        while(v[head].pos<i-m+1)
            head++;
        mx[i-m+1]=v[head].x;
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1; i<=n; i++)
        scanf("%d",&a[i]);
    getmin();
    for(int i=1; i<n-m+1; i++)
        cout<<mn[i]<<" ";
    cout<<mn[n-m+1]<<endl;
    getmax();
    for(int i=1; i<n-m+1; i++)
        cout<<mx[i]<<" ";
    cout<<mx[n-m+1]<<endl;
}


HDU 1506:http://acm.hdu.edu.cn/showproblem.php?pid=1506
单调栈:顾名思义,就是单调递增或单调减的栈,跟单调队列差不多,但它只用到一端。时间复杂度O(n)
性质:1、单调栈里的元素具有单调性
           2、元素加入栈前,会在栈顶端把破坏栈单调性的元素都删除
           3、使用单调栈可以找到元素向左遍历第一个比他小的元素(单调递增的栈),也可以找到元素向左遍历第一个比他大的元素(单调递减的栈)。
       单调栈解决的是以某个值为最小(最大)值的最大区间。实现方法是:求最小值(最大值)的最大区间,维护一个递增(递减)的栈,当遇到一个比栈顶小的值的时候开始弹栈,弹栈停止的位置到这个值的区间即为此值左边的最大区间;同时,当一个值被弹掉的时候也就意味着比它更小(更大)的值来了,也可以计算被弹掉的值得右边的最大区间。PS:借鉴别人的描述,大概就是这个道理

CODE:

#include<stdio.h>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
#include<math.h>
#include<map>
#include<queue>
#include<stack>
#include<string.h>
typedef long long LL;
using namespace std;
#define INF 0x3f3f3f3f
const int maxn=100005;
const double eps=0.0001;
struct node
{
    LL h,w;//h表示高度,w是不在栈里,但最小高度是h的有多少个,即:宽度
} c[maxn];
stack<node>s;
int main()
{
    int n;
    while(~scanf("%d",&n)&&n)
    {
        LL ans=0,cnt=0;
        while(!s.empty())
            s.pop();
        c[0].h=0;
        c[0].w=0;
        s.push(c[0]);
        for(int i=1; i<=n; i++)
        {
            scanf("%d",&c[i].h);
            c[i].w=1;
        }
        for(int i=1; i<=n; i++)
        {
            if(c[i].h>s.top().h)
            {
                c[i].w=1;
                s.push(c[i]);
            }
            else
            {
                cnt=0;
                while(!s.empty()&&s.top().h>c[i].h)
                {
                    ans=max(ans,(cnt+s.top().w)*s.top().h);
                    cnt+=s.top().w;//cnt表示要弹出的这个高度作用的范围(包括同一个栈中,已经弹出宽度的总和)
                     //栈底的元素肯定能作用整个栈中所有高度的宽度总和(这点要好好理解),因为栈是单调递增的
                    s.pop();
                }
                c[i].w=cnt+1;
                s.push(c[i]);
            }
        }
        cnt=0;
        while(!s.empty())
        {
            ans=max(ans,(cnt+s.top().w)*s.top().h);
            cnt+=s.top().w;
            s.pop();
        }
        printf("%lld\n",ans);
    }
    return 0;
}

单调队列和单调栈类似,其实都是模拟的过程。如有错误,欢迎指正~~~


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值