题意:给定一个整数 M,对于任意一个整数集合 S,定义“校验值”如下:从集合 S 中取出 M 对数(即 2∗M 个数,不能重复使用集合中的数,如果 S 中的整数不够 M 对,则取到不能取为止),使得“每对数的差的平方”之和最大,这个最大值就称为集合 S 的“校验值”。现在给定一个长度为 N 的数列 A 以及一个整数 K。我们要把 A 分成若干段,使得每一段的“校验值”都不超过 T。求最少需要分成几段。((1<=N,M<=500000,K<=10^{18}))
分析:从头开始对序列A进行分段,对于每段的起点L,要找到一个右端点R满足该段校验值小于K的情况下使R最大,这样才能保证段数最少.运用倍增的思想求R:
1,初始化p=1,r=l
2,求出[l,r+p]的校验值,若校验值<=K,则r+=p,p*=2,否则p/=2
3,重复上一步,知道p的值变为0,此时r即为所求
//#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define LL long long
using namespace std;
inline LL read(){
LL x=0,o=1;char ch=getchar();
while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
if(ch=='-')o=-1,ch=getchar();
while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
return x*o;
}
const int N=500005;
int a[N],b[N],c[N];
inline void merge(int l,int mid,int r,int a[]){
int i=l,j=mid+1,k=l;
while(i<=mid&&j<=r)
if(a[i]<a[j])c[k++]=a[i++];
else c[k++]=a[j++];
while(i<=mid)c[k++]=a[i++];
while(j<=r)c[k++]=a[j++];
}
inline LL solve(int l,int r,int m,int a[]){
LL cnt=0;
while(l<=r&&m)cnt+=(LL)(a[r]-a[l])*(a[r]-a[l]),++l,--r,--m;
return cnt;
}
int main(){
int T=read();
while(T--){
int n=read(),m=read();LL k=read();
for(int i=1;i<=n;++i)a[i]=read();b[1]=a[1];
int l=1,r=1,p=1,ans=1;
while(r<n){
int rr=r+p;if(rr>n)rr=n;
for(int i=r+1;i<=rr;++i)b[i]=a[i];
sort(b+r+1,b+rr+1);merge(l,r,rr,b);
if(solve(l,rr,m,c)>k)p/=2;
else{
r=rr;p*=2;
for(int i=l;i<=r;++i)b[i]=c[i];
}
if(!p)l=r+1,p=1,++ans;
}
printf("%d
",ans);
}
return 0;
}