題面入口: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;
}