AcWing 109. 天才ACM(倍增 归并 数据范围)

1 篇文章 0 订阅
1 篇文章 0 订阅

题干:

给定一个整数 M,对于任意一个整数集合 S,定义“校验值”如下:

从集合 S 中取出 M 对数(即 2∗M个数,不能重复使用集合中的数,如果 S 中的整数不够 M 对,则取到不能取为止),使得“每对数的差的平方”之和最大,这个最大值就称为集合S的“校验值”。

现在给定一个长度为 N 的数列 A 以及一个整数 T。

我们要把 A 分成若干段,使得每一段的“校验值”都不超过 T。

求最少需要分成几段。
1≤K≤12
1≤N,M≤500000
0≤T≤ 1 0 18 10^{18} 1018
0≤Ai≤ 2 20 2^{20} 220

思路

参考自《算法竞赛进阶指南》和这篇博客

根据贪心原则,每次选最大的数减去最小的数得到的每对数的差的平方”之和最大,则校验值可以由sum+=(mx-mi)*(mx-mi)得到(mx和mi每次更新)
然后为了快速得到mi和mx,我们可以提前将数组排序,考虑到我们还需要使用原数组,所以我们需要一个来记录。

首先需要得到一个最长的满足条件的队列,作为一次划分。这样我们需要枚举长度d,又因为1≤N≤500000,所以可以考虑到二分范围,但当T比较小的时候,需要的d较小而二分到小和大值都比较慢,所以为了解决这个问题可以使用倍增的方法,即d从1开始,如果合适 d ∗ * =2,否则d/=2.

这样我们得到了需要排序的区间,但是直接快排时间还是不大够,考虑到归并排序是每次分成两部分,则我们可以得到两个已经排好的序列,这样我们在下一次倍增的时候可以减少重复运算。

最后注意T的范围爆了int,所以在存值的时候要用long long

#include <cstdio>
#include <cstring>
#include <cmath>
#include <queue>
#include <map>
#include <iostream>
#include <algorithm>
#define ll long long
using namespace std;
int k,n,m;
ll a[500010],b[500010],t;

bool check(vector<int> vector);

bool combine(int l, int r, int nr){
    sort(b+1+r,b+1+nr);
    vector<int>  v;
    int q=l,p=r+1;
    while(q<=r&&p<=nr){
        if(b[q]<b[p])  v.push_back(b[q++]);
        else    v.push_back(b[p++]);
    }
    while(q<=r)
        v.push_back(b[q++]);
    while(p<=nr)
        v.push_back(b[p++]);
    if(check(v)){
        for(int i=0,j=l;i<v.size();i++,j++){
            a[j]=b[j]=v[i];
            
        }
        return true;
    } else{
        for(int i=r+1;i<=nr;i++){
            b[i]=a[i];
        }
        return false;
    }
}

bool check(vector<int> v) {
    if(v.size()<=1)   return true;
    ll s=0;
    int l=0,r=v.size()-1,sum=m;
    while(l<r&&sum){
        ll c=v[r]-v[l];
        s+=c*c;
        if(s>t)  return false;
        l++;r--;sum--;
    }
    return true;
}

int main()
{
    scanf("%d",&k);
    while(k--){
        scanf("%d%d%lld",&n,&m,&t);
        for(int i=0;i<n;i++){
            scanf("%lld",&a[i]);
            b[i]=a[i];
        }
        int l=0,r=0,ans=0;
        while(l<n){
            int nr=0,d=1;
            while(d)
            {
                nr=r+d;
                if(nr<n&&combine(l,r,nr))   {
                    d=d*2;
                    r=nr;
                }
                else   d=d/2;
            }
            ans++;
            l=r=min(r+1,n);
        }
        printf("%d\n",ans);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值