题解:
- 按照蓝书介绍,我们另l,r表示一段区间,p为要测试区间长度,初始时l=r=0,p=1;
- 1> 根据题意,要让分的区间个数越小,那么每个区间尽量大。 所以我们做这道题思考切入点,就是对区间左边界l,这个区间的右边界最大到哪里?
- 2> 最初r=l,p=1.当[l,r+p]这个区间的校验值<=k时,更新r,p ( r+=p , p*=2 ).如果[l,r+p]区间的校验值>k,那么缩小所要测试的区间长度p(p/=2)再测试,直到p==0,这时[l,r]区间以及不能再增加了,所以另r++,l=r,p=1。这也就产生了第一个区间,维护答案(ans++)
- 其实这个check()就跟二分一样,倍增貌似可以理解为反向二分,二分先是一个大区间[1,n],再向下缩小区间,而倍增先是处理[l,l+1],再处理[l,l+2],[l,l+4],[l,l+8]···,不断扩大区间。这对理解倍增很有用。
- 这道题不用二分是因为如果k很小,那么你每次都要把大区间二分计算到很小才得到答案,不如从头遍历处理的复杂度,而倍增就是从左到右增大处理区间的。所以倍增比较适合处理这种题,就是用二分做某类条件下不如遍历的题。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MA=5e5+5;
ll n,m,k,T;
ll a[MA],help[MA];
ll l,r,p,ans;
bool check(int l,int r){ //对[l,r+p]这一区间求校验码并与k比较
if(r>=n) return false;
int cnt=0;
for(int i=l;i<=r;++i) help[cnt++]=a[i];
sort(help,help+cnt);
ll sum=0;
for(int i=0;i<m&&i<(r-l+1)/2;++i) sum+=abs(help[r-l-i]-help[i])*abs(help[r-l-i]-help[i]);
return sum<=k;
}
void solve(){ //蓝书上倍增过程实现
while(r<n){
if(!p) ans++, r++, l=r, p=1;
if(check(l,r+p)) r+=p, p*=2;
else p/=2;
}
printf("%lld\n",ans);
}
void init(){ //初始化
l=r=0;
p=1;
ans=0;
}
int main()
{
//k与T意义交换一下,个人习惯
scanf("%lld",&T);
while(T--){
init();
scanf("%lld%lld%lld",&n,&m,&k);
for(int i=0;i<n;++i) scanf("%lld",&a[i]);
solve();
}
return 0;
}