题外话: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到了3e63e63e6,nlognlognnlognlognnlognlogn的做法不太珂靠。所以我们考虑强行套使用单调队列。
(由于博主刚刚学单调队列,所以只能强行套)
(考虑到读者很多也是初学单调队列,所以我站在了一个十分好的写博客角度。。。)
显然,要求最大和最小,就要维护两个优先队列,取名QxQxQx和QnQnQn。(在代码中,以xxx结尾的命名就是和求最大值有关的,以nnn结尾就是和求最小值有关的)。QxQxQx要保证单调递减,那么QxQxQx的队首就是最大值。同理,QnQnQn要保证单调递增,那么QnQnQn的队首也就是最小值。以后为了叙述方便,设FxFxFx为QxQxQx的队首,FnFnFn为QnQnQn的队首。
经过一段时间的考虑,我们发现,Fx−Fn>kFx-Fn>kFx−Fn>k,那么我们当前就一定包含了一个不合法的区间,那么这两个队首肯定有至少一个要被删掉。那么,具体删哪个呢?
答案是删在原数组中位置靠前的。为什么
意会一下,或者特殊值体会法(我最喜欢用这个方法,代入一个不那么具有偶然性的特殊值,然后手动模拟,感受这个选择的正确性和机智性,学信息竞赛的一定要会用这个方法)
为了方便求出原数组中的位置,在维护单调队列的时候,直接存数据的下标,然后判FxFxFx和FnFnFn哪个小,删掉对应的那个。
那么现在就只剩下合法的区间了。如何计算这个区间的长度呢?
维护一个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;
}