题目大意:给定一个长度为n的序列和一个数k,求最大数与最小数的差小于等于k的子序列的最长长度,并输出有多少个,以及各自的左端点和右端点。
不得不说这道题目真是经典。刚开始看到这道题的时候,就觉得要用线段树做了,但始终不知道该怎么搞,我当时的想法很暴力,枚举l,r用线段树暴力判断是否满足,显然T得很惨。看题解有一句关键的话:“若[l,r]满足条件,则[l,r-1]也必然满足条件”。这句话说明了单调性,于是我便自然而然地想到了枚举l,二分查找一个最靠右的r。二分的框架出来了我们如何判断一个区间是否成立呢?如果使用线段树,总时间复杂度是O(nlog^2n)的,效率很低,但是800ms可过。还有一种就是Sparse-Table算法,也就是RMQ问题,用nlogn的时间预处理后O(1)查询,这样复杂度就优化到了O(nlogn)。注意预处理时n一定要倒着枚举!
%%%__debug大神。
#include<cstdio>
#include<utility>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<vector>
#define x first
#define y second
const int MAXN=100000+10;
typedef std::pair<int,int> pii;
int w[MAXN],max[MAXN][20],min[MAXN][20],n,k,cnt=0,lenans=0;
std::vector<pii> ans;
void Pre()
{
for(int i=n;i>=1;i--)
{
for(int j=1;i+(1<<j)-1<=n;j++)
{
max[i][j]=std::max(max[i][j-1],max[i+(1<<(j-1))][j-1]);
min[i][j]=std::min(min[i][j-1],min[i+(1<<(j-1))][j-1]);
}
}
}
int findMaxMin(int l,int r)
{
int len=r-l+1,k=0,x=1;
while(x<=len)
{
k++;
x<<=1;
}k--;
return std::max(max[l][k],max[r-(1<<k)+1][k])-std::min(min[l][k],min[r-(1<<k)+1][k]);
}
int main()
{
memset(min,0x3f3f3f3f,sizeof(min));
scanf("%d %d",&n,&k);
for(int i=1;i<=n;i++)
scanf("%d",&w[i]),max[i][0]=min[i][0]=w[i];
if(n==1)
{
printf("1 1\n%d %d\n",w[1],w[1]);
return 0;
}
Pre();
for(int i=1;i<=n;i++)
{
int l=i,r=n;
//if(l==r)continue;
int flag=findMaxMin(i,r);
if(flag<=k&&flag>=0){l=r;goto loop;}
while(l+1!=r)
{
int mid=(l+r)>>1;
if(findMaxMin(i,mid)<=k)
l=mid;
else
r=mid;
}
loop: if(lenans<l-i+1)
{
lenans=l-i+1;
ans.clear();
ans.push_back(std::make_pair(i,l));
}
else if(lenans==l-i+1)
ans.push_back(std::make_pair(i,l));
}
printf("%d %d\n",lenans,ans.size());
for(int i=0;i<ans.size();i++)
printf("%d %d\n",ans[i].x,ans[i].y);
}