hihocoder1384 Genius ACM(倍增)

题目

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

现在给定一个长度为 n 的数列 A 以及一个整数 k。我们要把 A 分成若干段,使得 每一段的“校验值”都不超过 k。求最少需要分成几段。


题解

倍增+排序+暴力

题意中“每对数的差的平方”之和最大是首要解决任务,根据数学猜想,很容易想到把最大和最小组合,把次大和次小组合,以此类推。这里就涉及到了顺序问题。
朴素做法很容易想到,从1往n推,一旦超过k,就切换到下一个区间。让每个区间都尽量大,最终的段数一定最小。
这样的枚举实在太慢了,要优化有两条路可以走,一是二分,二是倍增,我选择了倍增做法。
其实倍增和二分待解决的问题是一样的,都是要做一个判断l~r的校验值是否在k之内。
第一步,最麻烦的,使区间内的元素有序。最简单的方法就是每加一个数进来就排一次序,可是TLE了。优化一下,把新增区间用归并排序并到原区间中。但是不要着急赋给它,因为有可能会失败。要等到符合题意后,再赋值。
第二步,求出该区间最大的“每对数的差的平方”之和。

第三步,判断要增大r,还是坎半p。


代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn=5e5+10;


inline ll read()
{
	ll re=0;char ch=getchar();
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') re=re*10+(ch^48),ch=getchar();
	return re;
}


int n,m;ll k;
int a[maxn],b[maxn],c[maxn];
//a存贮原始数据 b存当前排好序的序列 c存最新排好序的序列(注意:只在特定范围内有序) 


void merge(int l,int r,int mid)//合并b l~mid-1和mid~r 
{
	int i=l,j=mid;
	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++];
//	for(int k=l;k<=r;k++) b[k]=c[k];//推迟 
}


ll ans,p,l,r;
void mysort(int al,int ar)
{
	for(int i=r+1;i<=ar;i++) b[i]=a[i];
	sort(b+r+1,b+ar+1);
	merge(al,ar,r+1);
}


ll calc(int al,int ar)
{
	mysort(al,ar);
	ll re=0,cnt=0;
	for(int i=al,j=ar;i<j;i++,j--)
	{
		re+=(ll)(c[i]-c[j])*(c[i]-c[j]);//用最新的排好序的c来求校验值 
		cnt++;if(cnt==m) break;
	}
	return re;
}


int main()
{
	int T;scanf("%d",&T);
	while(T--)
	{
		n=read();m=read();k=read();
		for(int i=1;i<=n;i++) a[i]=read();
		
		ans=0,l=1;
		while(l<=n)
		{
			ans++;
			r=l;p=1;
			b[l]=a[l];//p=0先放入a[l]
			while(p!=0)
			{
				if( r+p<=n && calc(l,r+p)<=k )
				{
					r+=p;p<<=1;//倍增 
					for(int i=l;i<=r;i++) b[i]=c[i];//正式赋值 
				}
				else p>>=1;//倍减 
			}
			l=r+1;
		}
		printf("%lld\n",ans);
	}
	return 0;
}


猜你喜欢

转载自blog.csdn.net/a_bright_ch/article/details/81045434