【倍增】 Genius acm

传送门

题意

给定一个长度为\(n\)的整数序列\(a\),以及一个数\(t\),和一个\(m\),将\(a\)分成子序列,子序列中满足从中取出\(m\)对数(不能重复取,如果不够取到不能取为止),使得每一对数的差的平方和最大,
将这个最大值定义为序列的校验值,使得子序列的校验值小于等于\(t\),问最少能够分成多少个子序列

数据范围

\(\begin{array}{l}1 \leq K \leq 12 \\ 1 \leq N, M \leq 500000 \\ 0 \leq T \leq 10^{18} \\ 0 \leq A_{i} \leq 2^{20}\end{array}\)

题解

对于一段序列来说,取最大的和最小的组成一对,这样计算出来的和是最大的

  • 初始化\(p=1,r = l\)

  • 求出\([ l , r+p ]\)的校验值

    • 如果校验值\(<=t, r=r + p,p=p\times 2\),否则\(p= \frac{p}{2}\)
  • 重复上步,直到\(p=0\)

  • 以上的过程最多进行\(O(logn)\)次,每次循环都对$ r-l$ 排序,总体为O\((nlog^{2}n)\),每次求校验值的时候不需要快速排序,用类似归并排序的算法只需要对新增部分排序

Code

#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,n) for(int i=a;i<n;i++)
#define per(i,a,n) for(int i=n-1;i>=a;i--)

const int N=5e5+10;
int _;
int n,m;
ll t;
int a[N],b[N],c[N];
int last;
void merge(int l,int mid,int r){   
    int i = l,j = mid+1;
    for(int k=l;k<=r;k++){
        if(j > r || (i <= mid && b[i] < b[j]))
            c[k] = b[i++];
        else c[k] = b[j++];
    }
}
long long cal(int l,int r){
    if(r > n) r = n;
    int cp = min(m,(r-l+1)/2);
    for(int i=last+1,i<=r;i++) b[i] = a[i];
    sort(b+last+1,b+r+1);
    merge(l,last,r);
    ll res=0;
    rep(i,0,cp) 
        res += 1ll * (c[r-i]-c[l+i])*1ll*(c[r-i]-c[l+i]);
    return res;
}

void solve()
{
    cin>>n>>m>>t;
    for(int i=1;i<=n;i++) cin>>a[i];
    last=1;
    int ans=0,l=1,r=1;
    b[1]=a[1];
    while(l<=n){
        int p=1;
        while(p){  
            long long res = cal(l,r+p);
            if(res <= t){
                last = r = min( r+p , n );
                for(int i=l;i<=r;i++) b[i]=c[i]; // 保存有序形式
                if(r==n) break;
                p*=2;
            }
            else p/=2;
        }
        ans++;
        l=r+1;
    }
    cout<<ans<<endl;
}   

int main(){
    cin>>_;
    while(_--)
        solve();
}
posted @ 2020-06-05 00:12  Hyx'  阅读(104)  评论(0编辑  收藏  举报