题面入口:https://www.acwing.com/problem/content/111/
题目大意,将一个数列划分成最少的几段,满足每段内的数据集合中,取M对最大最小数出来,将其取出来的每对数求差值并平方,并求这M对的差值平方的求和值S,这个值不能超过指定的T。
题目分析:
从划分后的集合中选出M对数,让每对数的差的平方的和最大值为一个贪心模型,我们只需要将集合中的元素按从小到大排序,然后把最大数和最小数配对,再将次大数和次小数配对,依次类推即可。
为了划分尽可能少的区间,那么只要每个区间都尽可能大,那么就是最优方案,于是原问题也就转化为确定了一个左端点,右端点在哪个位置,使权值最大化,不超过T。
确定右端点一个个往后加并试探是否不超过T,时间复杂度会比较大。那么我们可以增加的方式变得有规律,步子要大,可以用倍增的思想。假设当前处理的区间是[left,right],尝试待确定的右端点newright,以及一次要往后增加的长度len。
因此我们接下来操作可以这样,先令newright=right + 2 * len;
,然后计算区间[left , newright]的权值与T的关系,如果比T大,则令len/2,否则right = newright,len*2;。
在计算一个范围内的取m对的差值平方和,便捷的方式是将其有序,两端取后计算。那么需要要对区间进行排序,在新增加区间的时候,不用全部重新排一遍,根据维护的思想,我们已经有了一段序列是有序的,将新增加的区间进行排序,然后二路归并到已确定的序列中让其有序,可以提高速度。
具体代码如下:
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MAXN = 500000+6;
int a[MAXN],b[MAXN],t[MAXN];
LL T;
int n,m,k;
//ºÏ²¢Á½¶ÎÓÐÐòµÄÇø¼äÖµ
void merge(int left,int right,int newright){
int i = left ,j = right+1;
for(int k = left ; k<= newright; k++){
if(j> newright ||(i<= right && b[i] <= b[j]))
t[k] = b[i++];
else
t[k] = b[j++];
}
}
//¼ÆËãÓÐÐò·¶Î§left - rightÖ®¼äµÄM¶Ô²îֵƽ·½µÄºÍ¡£
LL calc(int left,int right){
LL sum = 0;
for(int i = left ,j = right,k = m; i<j && k >0 ; i++,j--,k--){
sum = sum +1LL * (t[i]-t[j])*(t[i]-t[j]);
}
return sum;
}
int main(){
scanf("%d",&k);
while(k--){
int cnt = 0;
scanf("%d%d%lld",&n,&m,&T);
for(int i = 1;i<= n; i++){
scanf("%d",&a[i]);
}
b[1] = a[1];
int left = 1,right = 1,newright,len = 1;
while(right < n){
newright = right + len;
//Èç¹û³ö½çÔò»Øµ½n.
if(newright > n ) newright = n;
//¸´ÖÆright+1 -- newright Ö®¼äµÄÊýÖÁbÊý×éÖб¸Óá£
for(int i = right + 1; i <= newright; i ++) b[i] = a[i];
sort(b+ right+1, b+ newright + 1);
merge(left,right,newright);
LL sumpow = calc(left,newright);
if(sumpow > T) len = len /2;
else{
right = newright;
len = len * 2;
for(int i = left; i<= right ; i++) b[i] = t[i];
}
if(len == 0) {
//Ò»¸ö·Ö¶Î½áÊø£¬ÐµķֶοªÊ¼¡£
left = right + 1;
len = 1;
cnt ++;
}
}
printf("%d\n",cnt + 1);
}
return 0;
}