这个题目如果直接暴力枚举连续子区间是肯定会超时的;其实枚举子区间的时候会有很多重复的计算,我们可以利用这个性质.假设我们现在我们枚举起点为i,终点为j-1的区间是满足要求的,但是由于a[j]的值大于最大值或小于最小值,导致Max-Min>k,那么如果我们是暴力枚举的话,我们会将起点设为i+1然后继续枚举,但是其实如果a[i+1]点的值没有改变最大值或最小值则区间的终点仍然是j,不会得到一个更优的解.所以我们下次枚举的起点应该是第一个改变了最大值/最小值而使得当前区间Max-Min<=k的坐标.
要快速的确定这个坐标是这个题目的关键所在;开始的时候我用线段数保存区间最小值,最大值,然后用二分的方法进行查询,但是这样超时了.后面发现这个题目不需要修改只需要查询.所以我们可以直接用线段树查询到最大最小值的坐标.
PS:后面在网上看到可以用单调队列实现上面的功能.
代码如下:
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
#define ll long long
int const maxn=1100000;
ll Min[maxn*4],Max[maxn*4],a[maxn];
ll n,k;
void Build(ll rt,ll l,ll r)
{
if(l>=r)
{
Max[rt]=l;
Min[rt]=r;
return ;
}
ll m=(l+r)>>1;
Build(rt<<1,l,m);
Build(rt<<1|1,m+1,r);
Max[rt]=a[Max[rt<<1]]>a[Max[rt<<1|1]]?Max[rt<<1]:Max[rt<<1|1];
Min[rt]=a[Min[rt<<1]]<a[Min[rt<<1|1]]?Min[rt<<1]:Min[rt<<1|1];
}
ll query_max(ll rt,ll l,ll r,ll ql,ll qr)
{
if(ql<=l&&qr>=r)
{
return Max[rt];
}
ll xx1=ql,xx2;
ll m=(l+r)>>1;
if(m>=ql)
xx1=query_max(rt<<1,l,m,ql,qr);
if(m<qr)
{
xx2=query_max(rt<<1|1,m+1,r,ql,qr);
if(a[xx1]<a[xx2])
xx1=xx2;
}
return xx1;
}
ll query_min(ll rt,ll l,ll r,ll ql,ll qr)
{
if(ql<=l&&qr>=r)
{
return Min[rt];
}
ll m=(l+r)>>1;
ll xx1=ql,xx2;
if(m>=ql)
xx1=query_min(rt<<1,l,m,ql,qr);
if(m<qr)
{
xx2=query_min(rt<<1|1,m+1,r,ql,qr);
if(a[xx1]>a[xx2])
xx1=xx2;
}
return xx1;
}
int main()
{
while(scanf("%lld%lld",&n,&k)!=EOF)
{
for(int ii=1;ii<=n;ii++)
scanf("%lld",&a[ii]);
Build(1,1,n);
ll l=1,r=1;
ll Ma=1,Mi=1;
ll ans=1;
while(1)
{
r++;
if(r>n)
break;
if(a[r]>=a[Mi]&&a[r]<=a[Ma])
{
if(ans<r-l+1)
ans=r-l+1;
}
else if(a[r]<a[Mi])
{
Mi=r;
while((a[Ma]-a[Mi])>k)
{
l=Ma+1;
Ma=query_max(1,1,n,l,r);
}
if(ans<r-l+1)
ans=r-l+1;
}
else
{
Ma=r;
while((a[Ma]-a[Mi])>k)
{
l=Mi+1;
Mi=query_min(1,1,n,l,r);
}
if(ans<r-l+1)
ans=r-l+1;
}
}
printf("%lld\n",ans);
}
return 0;
}
时间: 2024-10-07 20:04:40