您的位置:首页 > 其它

AcWing 109 天才ACM

2019-03-29 23:42 169 查看

题目描述:

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

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

输入格式

第一行输入整数 K,代表有 K组测试数据。对于每组测试数据,第一行包含三个整数 N,M,T。

第二行包含 N个整数,表示数列A1,A2…AN。

输出格式

对于每组测试数据,输出其答案,每个答案占一行。

数据范围

1≤K≤12,1≤N,M≤500000,0≤T≤10^18,0≤Ai≤2^20

输入样例:

[code]2
5 1 49
8 2 1 7 9
5 1 64
8 2 1 7 9

输出样例:

[code]2
1

分析:

第一步:理解校验值的概念,在集合中取出M对数,使每对数差的平方和最大,这个最大的平方和就是校验值。那么什么情况下校验和最大呢?可以采取贪心的策略,M对数,每对数差的平方尽可能大,则最后相加求得的校验值也就是最大。也就是局部最优推出整体最优。比如3,1,2,6,4,5。想要求出校验和,需要先排序成1,2,3,4,5,6.每次取最大的数和最小的数,试想一下M = 2时,取1,6和2,5得到校验和为25 + 9 = 34.但若是取1,5和2,6得到16 + 16 = 32 < 34。可以证明:a < b < c < d时。(d - a)^2 + (c - b)^2 > (c - a) ^2 + (d - b)^2。左边减右边即可得证。所以我们的主要思路就是对集合进行排序,然后每次取最大最小值求校验和。

第二步:如何才能做到分成的段数最小,同样是贪心,使得每段集合的校验值在合法的前提下尽可能的长。有几种思路:最简单的是暴力枚举,每次将集合的长度加一看看是否其校验值合法。每次判断校验值都是需要重新排序的,这样做时间复杂度是我们所不能承受的。还有一种就是二分的思路,每次取总集合长度一半来判断校验值,但是一旦某个集合较短,而总集合很长,二分的方法必然会产生慢收敛现象,很久才能搜到解。于是我们采用倍增的策略来判断集合应该是多长。第一次长度加一,合法就加2,还合法就加4,每次增加长度翻倍。一旦发现不合法则立即收缩增加的长度,每次收缩的长度减半。这样一来,一方面在解较近的情况下能够很快搜索到解;另一方面当解较远时,由于每次搜索范围指数式增长,同样可以很快搜索到解。

第三步:优化复杂度,尽管倍增的策略能够让我们减少很多搜索的时间,但是本题数据相当大,还是多组数据的测试,时间卡得很紧。比如31295764,开始步长为1,排序得到13,。然后步长为2,对1329再次排序得到1239,最后步长为4,对12395764排序得到12345679.可以发现,尽管倍增的策略使我们免于一步步匍匐前行,而是大步跨越。但是每次跨越都得重新排序,而且已经排好序的数并未很好利用起来。采用二路归并的思想来优化。比如1329,分为13和29,再归并。再比如1239 和 5764,左边序列有序,只需对右边排序得到4567,再进行归并。如此可节省掉接近一半的排序时间,能够达到题目的复杂度要求。

本题主要思路只有这几步,但是离AC距离依旧遥远。第一个问题就是每次倍增,我们要排完序才能判断校验值是否合法。不合法步长收缩固然简单,但是原序列却已经改变了。比如1239倍增到12395764,排序得到12345679,突然发现校验值超过了要求,此时需要收缩步长。原来是需要考察序列123957,但是排序后就会考察123456,很明显逻辑产生了错误。于是我们在归并排序时需要及时备份,一旦不合法需要恢复数组。另一个问题是中间数据,可以发现,本题的数据范围数列A并未超过int的表示范围,但是中间运行结果,求差的平方肯定会超过int的表示范围的,如果不采用long long存储中间结果,那么在数据很大时必然会WA。

[code]#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn = 500005;
int K,N,M,ans;
ll A[maxn],B[maxn];
ll T;

bool islegal(vector<int> v){
if(v.size() <= 1)  return true;
ll s = 0;
int p = 0,q = v.size() - 1,cnt = M;
while(p < q && cnt--){
ll gap = v[q] - v

;//gap必须定义为ll s += gap * gap;//中间结果会很大 if(s > T) return false; p++,q--; } return true; } bool combine(int l,int r,int nr){//归并 sort(B + r + 1,B + nr + 1); vector<int> v; int p = l,q = r + 1; while(p <= r && q <= nr){ if(B[p] < B[q]) v.push_back(B[p++]); else v.push_back(B[q++]); } while(p <= r) v.push_back(B[p++]); while(q <= nr) v.push_back(B[q++]); if(islegal(v)){ int i = l; for(auto x : v) A[i] = B[i] = x,i++;//校验值合法则改变原数组 return true; } else{ for(int i = r + 1;i <= nr;i++) B[i] = A[i];//校验值不合法则恢复备份数组 return false; } } void solve(){ int l = 0,r = 0; ans = 0; while(l < N){ int nr,d = 1; while(d){ nr = r + d; if(nr < N && combine(l,r,nr)) r = nr,d *= 2;//确定倍增合法时才倍增 else d /= 2;//不合法则步长减半 } ans++; r = l = min(r + 1,N); } } int main(){ cin>>K; while(K--){ cin>>N>>M>>T; for(int i = 0;i < N;i++) cin>>A[i],B[i] = A[i]; solve(); cout<<ans<<endl; } return 0; }

[p] 

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: