天才ACM

AcWing

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

分析:从头开始对序列A进行分段,对于每段的起点L,要找到一个右端点R满足该段校验值小于K的情况下使R最大,这样才能保证段数最少.运用倍增的思想求R:

1,初始化p=1,r=l

2,求出[l,r+p]的校验值,若校验值<=K,则r+=p,p*=2,否则p/=2

3,重复上一步,知道p的值变为0,此时r即为所求

//#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define LL long long
using namespace std;
inline LL read(){
    LL x=0,o=1;char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')o=-1,ch=getchar();
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    return x*o;
}
const int N=500005;
int a[N],b[N],c[N];
inline void merge(int l,int mid,int r,int a[]){
	int i=l,j=mid+1,k=l;
	while(i<=mid&&j<=r)
		if(a[i]<a[j])c[k++]=a[i++];
		else c[k++]=a[j++];
	while(i<=mid)c[k++]=a[i++];
	while(j<=r)c[k++]=a[j++];
}
inline LL solve(int l,int r,int m,int a[]){
	LL cnt=0;
	while(l<=r&&m)cnt+=(LL)(a[r]-a[l])*(a[r]-a[l]),++l,--r,--m;
	return cnt;
}
int main(){
	int T=read();
	while(T--){
		int n=read(),m=read();LL k=read();
		for(int i=1;i<=n;++i)a[i]=read();b[1]=a[1];
		int l=1,r=1,p=1,ans=1;
		while(r<n){
			int rr=r+p;if(rr>n)rr=n;
			for(int i=r+1;i<=rr;++i)b[i]=a[i];
			sort(b+r+1,b+rr+1);merge(l,r,rr,b);
			if(solve(l,rr,m,c)>k)p/=2;
			else{
				r=rr;p*=2;
				for(int i=l;i<=r;++i)b[i]=c[i];
			}
			if(!p)l=r+1,p=1,++ans;
		}
		printf("%d\n",ans);
	}
    return 0;
}

posted on 2019-07-23 21:43  PPXppx  阅读(343)  评论(0编辑  收藏  举报