P10059 Choose

· · 题解

博客食用更佳。

正篇开始

考虑题目中很重要的一句话:

很明显,要求最小值最大,显然二分答案。那么问题来了,二分什么呢?

现在有两个选择:二分极差、二分长度。

很明显,前者可以一次性都算出来,但是十分麻烦,无从下手;后者只能算出长度,但是二分思路很简单。这个时候,我们就需要发现一些性质了。

性质1:区间 [l,r] 的子区间求出的极差绝对没有该区间大。

性质2:所有长度为 l 的区间,都可以被长为 l+1 的区间包含。

根据这两个性质,显而易见就可以推导出结论:

结论:区间长度越长,答案或更优,或不变。

这样,一个贪心求极差的方法显然出现:直接枚举所有长度为 n-k+1 的区间,求出这些区间的极差最小值 X',第一个答案即为 X'

既然我们可以求出极差,那么我们就不需要二分极差,因此只能二分长度。但是问题接踵而至:如何快速计算区间极差呢?

解决这个问题,我们需要知道一件事情:区间极差=区间最大值-区间最小值。

问题转化为:如何快速求出静态区间最大/最小值?

用线段树自然可以,但是时间复杂度不如 RMQ。现在,我们只需要解决 \operatorname{check} 函数即可。

二分长度,当该长度的区间中,有大于等于 $k$ 个大于 $X$,返回 $1$,否则返回 $0$。 现在我们会发现,使用线段树,时间复杂度会多一个 $\log$,而根据分析,原先的时间复杂度为 $O(n\log_2n)$,已经容不得 $n\log^2_2n$ 了。 空间复杂度最大是 RMQ,空间复杂度 $O(n\log_2n)$,最好预处理 $\log$。 --- 标程 ```cpp #include<bits/stdc++.h> #define N 100005 #define M 1000005 using namespace std; int t,n,k,a[N],mx[M],mn[N],ans1; int lg[N],f[N][20],dp[N][20]; void build(){ for(int i=1;i<=100000;i++) for(int j=1;j<17;j++) f[i][j]=-1e9-1; for(int i=1;i<=100000;i++) for(int j=1;j<17;j++) dp[i][j]=1e9+1; for(int i=1;i<=n;i++) f[i][0]=dp[i][0]=a[i]; for(int j=1;j<17;j++) for(int i=1;i+(1<<j)-1<=n;i++){ f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]); dp[i][j]=min(dp[i][j-1],dp[i+(1<<(j-1))][j-1]); } }int qmax(int l,int r){ int zjy=lg[r-l+1]; return max(f[l][zjy],f[r-(1<<zjy)+1][zjy]); }int qmin(int l,int r){ int zjy=lg[r-l+1]; return min(dp[l][zjy],dp[r-(1<<zjy)+1][zjy]); }int check(int x){ int sum=0; for(int i=1;i<=n-x+1;i++){ int lyh=qmax(i,i+x-1); int zjy=qmin(i,i+x-1); if(lyh-zjy>=ans1) sum++; }return sum>=k; }int main(){ for(int i=1;i<N;i++) lg[i]=log2(i); cin>>t;while(t--){ cin>>n>>k;ans1=2e9+1; for(int i=1;i<=n;i++) cin>>a[i]; build(); for(int i=1;i<=k;i++) ans1=min(ans1,qmax(i,n-k+i)-qmin(i,n-k+i)); cout<<ans1<<" ";int l=1,r=n-k+1,ans2; while(l<=r){ int mid=(l+r)/2; if(!check(mid)) l=mid+1; else r=mid-1,ans2=mid; }cout<<ans2<<"\n"; }return 0; } ``` --- 赛后还有加强版,这会主要卡空间,所以无法支付 RMQ 的空间。 考虑单调队列。由于本题所需要的最大最小值,实际上是滑动窗口板子,所以可以将空间复杂度压缩至 $O(n)$。代码就不写了,留给大家探索。