Genius ACM

题意

给定一个整数 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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值