将该问题分解:
问题1:校验值应该如何取
问题2:如何把A分成若干段使得每一段的校验值都不超过T
问题一求校验值:
写一下公式(假设有k个数):
(a1-a2)2+(a3-a4)2+···+(ak-1-ak)2
=a12+a22+···+ak2-2*(a1a2+a3a4+···+ak-1ak)
由基本不等式可以知道,每对数中的两个数需要相差尽可能大,即我们只需要对s进行排序,最小和最大,次小和次大依次配对,奇数情况舍弃中间点即可
问题二分段:
我们要把A分成若干段,且每一段的校验值不超过T,求最少分成几段。即在我们确定左端点之后,满足A[L]~A[R]的校验值不超过T的前提下,R最大取多少。
如果我们直接暴力遍历,时间复杂度是O(N*NlogN)其中N是右端点会遍历所有位置,NlogN是对当前区间进行排序。会超时
考虑倍增,确定了左端点后,我们倍增地去找右端点。
假设当前的左端点是L,右端点是R,区间长度是len。
如果该区间满足条件,我们就让R+=len,len<<=1,
如果该区间不满足条件,就让len>>=1。
这样右端点只会走logN次,加上排序就是O(Nlog2N)
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 5e5+10;
int n,m,k;
ll t;
ll a[N];
ll calc(int l,int r){
static ll c[N];
int len=r-l+1;
for(int i=l;i<=r;i++)c[i-l+1]=a[i];
sort(c+1,c+1+len);
ll res=0;
for(int i=1;i<=len/2&&i<=m;i++)res+=(c[len-i+1]-c[i])*(c[len-i+1]-c[i]);
return res;
}
void solve(){
scanf("%d%d%lld",&n,&m,&t);
for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
int ans=0;
int L=1,R;
while(L<=n){
//cout<<L<<endl;
R=L+1;
int len=1;
while(len){
if(R+len-1<=n&&calc(L,R+len-1)<=t){
R+=len;
len<<=1;
}
else len>>=1;
}
ans++;
L=R;
}
printf("%d\n",ans);
}
int main(){
scanf("%d",&k);
while(k--)solve();
return 0;
}