• # Acwing 109. Genius ACM (倍增 + 归并排序)


    题意

    给出n个数Ai,问最少将这n个数分成几段,使每一段的校验值小于k。

    一段数的校验值定义为:

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

    (1<= N , M <=500000,0<=K<=1e18,0<=A_i<=2^{20})

    思路

    贪心很容易得到校验值,就是选最大和最小的差的平方+次大和次小的差平方+…...+。

    要想分段数最小,每一段就要尽可能多的数。对于一个左端点,使右端点尽可能大。

    一种很朴素的思路是一点一点扩展右端点,每次扩展排序算校验值,但是这样扩展太慢,并且涉及大量的快排,显然会超时。

    优化:
    扩展可以考虑使用倍增思想,以二进制的方式扩展。
    每次扩展其实只有扩展的部分需要重新排序,可以考虑只对扩展的部分使用快排,再使用归并排序合并。

    代码

    #include <bits/stdc++.h>
    using namespace std;
    #define doub(x) (x)*(x)
    typedef long long LL;
    const int maxn=5e5+5;
    int t,n,m,ans;
    LL k;
    int p[maxn],a[maxn];
    LL b[maxn];
    
    inline void merge(int l,int mid,int r){
        int i=l,j=mid,k=l;
        while (i<mid && j<=r){
            if(a[i]<a[j])b[k++]=a[i++];
            else b[k++]=a[j++];
        }
    
        while (i<mid)b[k++]=a[i++];
        while (j<=r)b[k++]=a[j++];
    }
    
    inline bool check(int l,int mid,int r){
        for(int i=mid;i<=r;++i)a[i]=p[i];
    
        sort(a+mid,a+r+1);
    
        merge(l,mid,r);
    
        LL sum=0;
        for(int i=0;i<m && r-i>l+i ;++i)
            sum+=doub(b[r-i]-b[l+i]);
    
        if(sum>k)return false;
    
        for(int i=l;i<=r;++i)a[i]=b[i];
        return true;
    }
    
    inline void sol(){
        ans=0;
        int l=0,r=0,len=1;
        a[l]=p[l];
        while (r<n){
            if(!len){
                ++ans;
                l=(++r);
                a[l]=p[l];
                len=1;
            }
            else if(r+len<n && check(l,r+1,r+len)){
                r+=len;
                if(r==n-1)break;
                len<<=1;//倍增思想,成倍增长
            }
            else len>>=1;//倍增思想,无法成倍增长时,尝试缩小一倍增长
        }
        if(r==n-1)++ans;
    }
    int main(){
        ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    
        cin>>t;
        while (t--){
            cin>>n>>m>>k;
            for(int i=0;i<n;++i)cin>>p[i];
    
            sol();
            cout<<ans<<endl;
        }
        return 0;
    }
    
  • 相关阅读:
    正则表达式
    70. 爬楼梯
    最大子序和
    动态规划
    中序遍历
    两数相加基础
    保证一个线程最后执行或者最先执行CyclicBarrier
    保证一个线程最后执行使用CountDownLatch
    两个线程交替执行
    单列模式double check的问题
  • 原文地址:https://www.cnblogs.com/sstealer/p/13297698.html
Copyright © 2020-2023  润新知