题意
给定一个整数 M,对于任意一个整数集合 S,定义“校验值”如下:
从集合 S 中取出 M 对数(即 2∗M 个数,不能重复使用集合中的数,如果 S 中的整 数不够 M 对,则取到不能取为止),使得“每对数的差的平方”之和最大,这个最大值 就称为集合 S 的“校验值”。
现在给定一个长度为 N 的数列 A 以及一个整数 T。我们要把 A 分成若干段,使得 每一段的“校验值”都不超过 T。求最少需要分成几段。
题解
显然得,一个段的校验值肯定是最大和最小配起来
然后就可以了
然后剩下的,就是对于一个L,二分他的右端点
然后暴力检验
这样的话是可以视为
nlog2n
n
l
o
g
2
n
的
但是这个
nlog2n
n
l
o
g
2
n
十分不标准,可能会大很多
考虑省常数
因为这个区间肯定特别小,所以我们可以用倍增来算,就可以变得很小了
然后倍增剩一个区间以后,在这个区间里面二分即可
CODE:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
typedef long long LL;
const LL N=500005;
LL T;
LL n,m,k;
LL a[N];
LL b[N];
LL tot;
bool check (LL l,LL r)//这一段合不合法
{
tot=0;
for (LL u=l;u<=r;u++) b[++tot]=a[u];
sort(b+1,b+1+tot);
LL lalal=0;
for (LL u=1;u<=min(tot/2,m);u++)
{
lalal=lalal+(b[tot-u+1]-b[u])*(b[tot-u+1]-b[u]);
if (lalal>k) break;
}
return lalal<=k;
}
bool cmp (LL x,LL y) {return a[x]<a[y];}
void prepare (LL l,LL r)
{
tot=0;
for (LL u=l;u<=r;u++) b[++tot]=u;
sort(b+1,b+1+tot,cmp);
}
bool check1 (LL x)
{
LL l=1,r=tot,now=0;
for (int u=1;u<=m;u++)
{
while (l<r&&b[l]>x) l++;
while (l<r&&b[r]>x) r--;
//printf("%lld %lld\n",b[l],b[r]);
if (l<r)
{
now=now+(a[b[l]]-a[b[r]])*(a[b[l]]-a[b[r]]);
l++;r--;
if (now>k) break;
}
else break;
}
//printf("now:%lld\n",now);
return now<=k;
}
int main()
{
scanf("%lld",&T);
while (T--)
{
scanf("%lld%lld%lld",&n,&m,&k);
for (LL u=1;u<=n;u++) scanf("%lld",&a[u]);
LL ans=0;
LL l=1;
while (l<=n)
{
LL ln=1;
while (ln+l<=n&&check(l,l+ln)) ln<<=1;
LL L=l+ln/2,R=min(l+ln-1,n);
// printf("%lld %lld\n",l,R);
prepare(l,R);
// for (int u=1;u<=tot;u++) printf("%d ",b[u]);
// printf("%d %d\n",check(1,n),check1(100));
LL lalal=L;
while (L<=R)
{
LL mid=(L+R)>>1;
// printf("%lld %lld %lld\n",L,R,mid);
if (check1(mid))
{
L=mid+1;
lalal=mid;
}
else R=mid-1;
}
// printf("lalal:%lld\n",lalal);
l=lalal+1;
ans++;
}
printf("%lld\n",ans);
}
return 0;
}