《算法竞赛进阶指南》0x06 T1 Genius ACM

该博客讨论了一道算法题目,涉及最大校验值的计算。在给定整数集合和特定条件(校验值不超过特定阈值)下,需要将数列分成若干段。博主提出了一种优化的解决方案,通过倍增算法在O(nlogn)的时间复杂度内解决,避免了暴力排序和枚举的方法。文章详细解释了倍增算法的实现过程,并给出了代码示例。
摘要由CSDN通过智能技术生成

题目传送门

题目描述

给定一个整数 M M M,对于任意一个整数集合 S S S,定义“校验值”如下:
从集合 S S S 中取出 M M M 对数(即 2 × M 2×M 2×M 个数,不能重复使用集合中的数,如果 S S S 中的整数不够 M M M 对,则取到不能取为止),使得“每对数的差的平方”之和最大,这个最大值就称为集合 S S S 的“校验值”。
现在给定一个长度为 N N N 的数列 A A A 以及一个整数 T T T
我们要把 A A A 分成若干段,使得每一段的“校验值”都不超过 T T T
求最少需要分成几段。

输入格式

第一行输入整数 K K K,代表有 K K K 组测试数据。
对于每组测试数据,第一行包含三个整数 N , M , T N,M,T N,M,T
第二行包含 N N N 个整数,表示数列 A 1 , A 2 … A N A_1,A_2…A_N A1,A2AN

输出格式

对于每组测试数据,输出其答案,每个答案占一行。

数据范围

1 ≤ K ≤ 12 , 1≤K≤12, 1K12,
1 ≤ N , M ≤ 500000 , 1≤N,M≤500000, 1N,M500000,
0 ≤ T ≤ 1 0 18 , 0≤T≤10^{18}, 0T1018,
0 ≤ A i ≤ 2 20 0≤A_i≤2^{20} 0Ai220

输入样例:

2
5 1 49
8 2 1 7 9
5 1 64
8 2 1 7 9

输出样例:

2
1

题解

这道题首先裸着打是很好写的,只需要暴力排序枚举瞎搞就行了
但很明显这样写不现实
对于这样大的数据范围,为了优化暴力,最直接的办法当然就是二分,时间复杂度为 O ( n 2 l o g n ) O(n^2logn) O(n2logn)(我也不会算)
于是就有了一个比二分更优的写法:倍增!
它的复杂度是 O ( n l o g n ) O(nlogn) O(nlogn)
因为要将区间排序,直接sort的话就会炸掉,所以可以利用归并排序,因为已经提前将某些数字排序过了
具体的倍增过程如下:
用倍增找出区间的长度,如果当前区间符合,就把伸长的长度翻倍,如果不符合就折半即可

#include<bits/stdc++.h>
using namespace std;
const int N=500005;
int n,m,ans;
long long T,w[N], t[N],tmp[N];
bool check(int l, int mid, int r) 
{
    for(int i=mid;i<r;i++) t[i]=w[i];
    sort(t+mid,t+r);
    int i=l,j=mid,k=0;
    while(i!=mid&&j!=r)
        if(t[i]<t[j]) tmp[k++]=t[i++];
        else tmp[k++]=t[j++];
    while(i!=mid) tmp[k++]=t[i++];
    while(j!=r) tmp[k++]=t[j++];
    long long sum=0;
    for(i=0;i<m&&i<k;i++,k--) sum+=(tmp[i]-tmp[k-1])*(tmp[i]-tmp[k-1]);
    return sum<=T;
}
int main()
{
    int K;
    scanf("%d",&K);
    while(K--)
    {
        scanf("%d%d%lld",&n,&m,&T);
        for(int i=0;i<n;i++) scanf("%lld",&w[i]);
        ans=0;
        int len;
        int start=0,end=0;
        while(end<n)
        {
            len=1;
            while(len)
            {
                if(end+len<=n&&check(start,end,end+len))
                {
                    end+=len,len<<=1;
                    if(end>=n) break;
                    for(int i=start;i<end;i++) t[i]=tmp[i-start];
                }
                else len>>=1;
            }
            start=end;
            ans++;
        }
        printf("%d\n",ans);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值