洛谷 3512 & bzoj 2096 题解

博客介绍了如何解决给定n和k以及一个序列,找到最长的连续子序列,使得最大值与最小值之差不超过k的问题。博主分享了使用线段树和二分长度的思路,但由于n的限制,提出了使用单调队列的方法。通过维护两个单调队列Qx和Qn,分别保持最大值和最小值,并删除位置靠前的不合法元素。通过特殊值模拟确定了删除策略,并详细说明了算法流程和代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题外话:bzoj2096现在找不到了。。。要交还是上洛谷。。。

题意简述

给定n,k和一个长度为n的序列,求最长的最大值最小值相差不超过k的连续序列(翻译的很清楚)。

数据

输入:
3 9
5 1 3 5 8 6 6 9 10
输出:
4

解释:

5 8 6 6 和 8 6 6 9都是正确的

思路

线段树+二分长度好像珂以做,但是只能拿808080分,因为n到了3e63e63e6nlognlognnlognlognnlognlogn的做法不太珂靠。所以我们考虑强行套使用单调队列。
(由于博主刚刚学单调队列,所以只能强行套)
(考虑到读者很多也是初学单调队列,所以我站在了一个十分好的写博客角度。。。)

显然,要求最大和最小,就要维护两个优先队列,取名QxQxQxQnQnQn。(在代码中,以xxx结尾的命名就是和求最大值有关的,以nnn结尾就是和求最小值有关的)。QxQxQx要保证单调递减,那么QxQxQx的队首就是最大值。同理,QnQnQn要保证单调递增,那么QnQnQn的队首也就是最小值。以后为了叙述方便,设FxFxFxQxQxQx的队首,FnFnFnQnQnQn的队首。

经过一段时间的考虑,我们发现,Fx−Fn>kFx-Fn>kFxFn>k,那么我们当前就一定包含了一个不合法的区间,那么这两个队首肯定有至少一个要被删掉。那么,具体删哪个呢?

答案是删在原数组中位置靠前的。为什么

意会一下,或者特殊值体会法(我最喜欢用这个方法,代入一个不那么具有偶然性的特殊值,然后手动模拟,感受这个选择的正确性和机智性,学信息竞赛的一定要会用这个方法)

为了方便求出原数组中的位置,在维护单调队列的时候,直接存数据的下标,然后判FxFxFxFnFnFn哪个小,删掉对应的那个。

那么现在就只剩下合法的区间了。如何计算这个区间的长度呢?

维护一个preprepre,表示我们现在考虑到的左端点。初始值是111(忘了说了,我们特判a[1]a[1]a[1],然后后面从222开始考虑),在每次弹出队首的时候要修改为队首+1(先改再弹,不然数据不对了)。

理一下思路:

  • 维护两个优先队列
  • 枚举iii从1~n,两个队列都推进去a[i]a[i]a[i]
  • 处理不合法的情况
  • 更新答案

(很清楚吧)
代码:

#include<bits/stdc++.h>
#define N 3001000
#define int long long
using namespace std;

int k,n;
int a[N];
void Input()
{
    scanf("%lld%lld",&k,&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",&a[i]);
    }
}

int Qx[N],Qn[N];
int hx,tx,hn,tn;
void Solve()
{
    hx=tx=hn=tn=1;
    //初始都是应该h=1,t=0,由于加了a[1]进去,所以导致两个t变成1
    Qx[1]=Qn[1]=1;//记得是存下标!!!
    int ans=1,pre=1;//ans应该从1开始,因为我们特判了a[1],这样保证n=1的时候也能正确
    for(int i=2;i<=n;i++)
    {
        while(hx<=tx and a[Qx[tx]]<a[i]) tx--;
        Qx[++tx]=i;
        while(hn<=tn and a[Qn[tn]]>a[i]) tn--;
        Qn[++tn]=i;
        //推进去

        #define Fn (Qn[hn])
        #define Fx (Qx[hx])
        //取队首
        //注意,这里不能用int,也不能用int&,必须要用#define
        //原因是hn和hx会变化,int和int&都无法随着这个变化而变化,只要#define珂以
        while(a[Fx]-a[Fn]>k)
        {
            if (Fx<Fn)//取靠前的那个踢掉
            {
                pre=Fx+1;//先更新pre
                hx++;//再弹队首
            }
            else
            {
                pre=Fn+1;//同上^^^
                hn++;
            }
        }
        ans=max(ans,i-pre+1);//更新答案
    }
    printf("%lld\n",ans);
}
main()
{
    Input();
    Solve();
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值