HDU 3530 题解

本文介绍了一种在序列中寻找最长连续子序列的算法,该子序列满足特定条件:其最大值与最小值之差在预设范围内。通过使用两个单调队列优化搜索过程,将时间复杂度从O(n^2)降低至O(n)。

题意简述

多组数据。给定一个序列,求一段最长的连续子序列使得这段区间最大值-最小值在[m,k][m,k][m,k]之内。

数据

输入
5 0 0
1 1 1 1 1//到此第一组
5 0 3
1 2 3 4 5//到此第二组

5 0 0
1 1 1 1 1
5 0 3
1 2 3 4 5

输出
5
4

思路

我们有一个暴力的思路:枚举区间起点iii,然后去后面找最长能有多长。这都会写,是O(n2)O(n^2)O(n2)的。
但是,我们要想想能不能改进一下这个思路。把枚举起点换成枚举终点,然后看看能不能继承上一个终点的答案,争取不重复计算,复杂度降到O(n)O(n)O(n)

怎么继承上一个枚举的答案呢?显然是个单调队列。
按套路需要维护两个单调队列,存的都是在原数组中的下标。
第一个单调队列(设为QxQxQx,它的头是hxhxhx,尾是txtxtx)是单调递减的,存最大值(头就是最大);
第二个单调队列(设为QnQnQn,它的头是hnhnhn,尾是tntntn),是单调递增的,存最小值(头就是最小)。
然后,每次枚举iii,也就是枚举区间结束点。这个枚举是绝对的,必须要以iii结尾,不会做出让步。因为iii不会让步,所以只能让前面珂怜的队列让步了。所以我们要把iii推进两个单调队列里面,挤掉别的元素,使得iii进去队列。当然,还要看两个队首相减(即最大-最小)是否大于kkk,如果大于kkk,就不满足条件,把在原数组中靠前的队首推掉。这样,我们要做的就只有在队尾挤掉一些元素,把iii推进去,去掉队首不满足条件的元素。这样一算,还真是O(n)O(n)O(n)

代码:

//代码千万行,注释第一行
#include<bits/stdc++.h>
#define N 1001000
using namespace std;
int a[N],n;
int Low,High;//也就是m,k(名字形象一点。。。自带注释。。。)

int Input1()
{
    int tmp=scanf("%d%d%d",&n,&Low,&High);
    if (tmp==-1) return -1;//不成功读入
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
    }
    return 0;//成功读入
}

int Qx[N],hx,tx;int Qn[N],hn,tn;
void Solve()
{
    hx=hn=tx=tn=1;//这个边界不一样一定要小心!!!

    int pre=0;//记录区间左端点(一定要是0!!!设成1就WA了!!!)
    int ans=0;//记录最大长度
    for(int i=1;i<=n;i++)
    {
        while(hx<tx and a[Qx[tx-1]]<=a[i]) tx--;
        Qx[tx]=i;++tx;//挤掉队尾

        while(hn<tn and a[Qn[tn-1]]>=a[i]) tn--;
        Qn[tn]=i;++tn;//挤掉队尾

        #define dif (a[Qx[hx]]-a[Qn[hn]])//写成define省码量
        while(dif>High)//如果不满足条件(如果dif<Low就没救了,出现就不管了。。。)
        {
            if (Qx[hx]>Qn[hn]) pre=max(pre,Qn[hn++]);
            else pre=max(pre,Qx[hx++]);//记录左端点
        }
        if (dif>=Low)//要判一下这个才能更新答案,要不然不满足条件也更新,就WA了
        {
            ans=max(ans,i-pre);
        }
    }
    printf("%d\n",ans);
}

void Query()
{
    while(1)
    {
        int tmp=Input1();
        if (tmp==-1) break;
        Solve();
    }
}
int main()
{
    Query();
    return 0;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值