题目大意:给定一个长度为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); }