牛客网 Genius ACM
题面
给定一个整数 M,对于任意一个整数集合 S,定义“校验值”如下:
从集合 S 中取出 M 对数(即 2∗M 个数,不能重复使用集合中的数,如果 S 中的整 数不够 M 对,则取到不能取为止),使得“每对数的差的平方”之和最大,这个最大值 就称为集合 S 的“校验值”。
现在给定一个长度为 N 的数列 A 以及一个整数 K。我们要把 A 分成若干段,使得每一段的“校验值”都不超过 K。求最少需要分成几段。
T≤12,1≤n,m≤5×105,0≤K≤118,0≤Pi≤220
分析
一个小小的结论:对于有序的\(a_1,a_2,\cdots,a_n\),校验值\(=(a_n-a_1)^2+(a_{n-1}-a_{2})^2 \cdots\)
然后可以倍增(神仙)
对于每个L,右端点已经做到R,当前倍增p,考虑R+p
如果[l,R+p]可行,则R+=p,p<<=1;反之,p>>=1
当p=0时结束
每次[L,R]合并上[R+1,R+p],不需要每次快排,可以只对[R+1,R+p]上的数进行排序后归并
对于每一个左端点,如果划分出的集合大小为V,本来就已经做了\(V log V\),只会多做\(V log V\)
所以时间复杂度还是\(O(nlgn)\)
注意细节,如果可以合并则合并,要将数组更新,使其有序
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=5e5+5;
int n,m,ans,a[N],b[N],c[N]; ll K;
int main(){
// freopen("1.in","r",stdin);
// freopen("1.out","w",stdout);
int T; scanf("%d",&T);
while(T--) {
scanf("%d%d%lld",&n,&m,&K);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
ans=0;
for(int L=1,R;L<=n;L=R+1) {
int p=1; R=L; b[L]=a[L];
while(p) {
if(R+p>n) {
p>>=1; continue;
}
for(int i=R+1;i<=R+p;i++) {
b[i]=a[i];
}
sort(b+R+1,b+R+p+1);
for(int i=L,j=R+1,k=L;k<=R+p;k++) {
if(j==R+p+1||i<=R&&b[i]<b[j]) {
c[k]=b[i],i++;
} else {
c[k]=b[j],j++;
}
}
ll sum=0;
for(int i=1;i<=m;i++) {
if(L+i-1<R+p-i+1) {
sum+=(ll)(c[R+p-i+1]-c[L+i-1])*(c[R+p-i+1]-c[L+i-1]);
if(sum>K) break;
} else break;
}
if(sum<=K) {
R+=p;
for(int i=L;i<=R;i++) {
b[i]=c[i];
}
p<<=1;
} else {
p>>=1;
}
}
// printf("%d %d\n",L,R);
ans++;
}
printf("%d\n",ans);
}
return 0;
}

浙公网安备 33010602011771号