大意:给出n个在一条直线上的点,允许移动其中的k个点,求移动k个点后,其余所有点到重心的距离的平方和最小。
思路:首先,5w个点,暴力解会超时。可以看出,k个点肯定是移动到最后 的重心处,所以这k个点就不用再考虑了。关于重心,因为所有点的质量都是1,因此将所有点的平均值就是重心。接下来就是解这一题的关键了,留下哪些点才能使公式 I 的值最小,试想一下,如果将n个点排序,那么,取连续的n-k个点的 I 的值肯定比不连续的小,既然这样的话,只需要从区间1~n-k枚举到区间k+1~n这k个长度为n-k的区间即可。
还有一点就是求公式 I 的时候,暴力解复杂度很大,不妨可以将公式推一下,然后化简,最终可以得到O(1)的求解复杂度
I = d1^2 + d2^2 + d3^2 + .... + dn^2
= (X1-X0)^2 + (X2-X0)^2 + .... + (Xn-X0)^2
= (X1^2+X2^2+X3^2+.....+Xn^2) - 2X0(X1+X2+....+Xn) + nX0^2
(X0表示重心,Xi表示第i个点)
从上面化简的结果可以看出,X1^2+X2^2+X3^2+.....+Xn^2 可以用一个sum2数组存,sum2[n]表示前n个x^2和,同理用sum[n]表示前n个x和,那么O(1)的求解复杂度就出来了
最后还有一点要注意的是,题目说误差要小于1e-9,那么结果要取到小数点后十位
AC代码:
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <iostream>
using namespace std;
double arr[50005],sum[50005],sum2[50005];
int main()
{
//freopen("input.txt","r",stdin);
int T,i,n,k,m;
double minm,aver,temp;
scanf("%d",&T);
while(T--) {
sum[0]=sum2[0]=0;
scanf("%d %d",&n,&k);
for(int i=1;i<=n;i++){
scanf("%lf",&arr[i]);
}
sort(arr+1,arr+n+1);
for(int i=1;i<=n;i++){
sum[i]=sum[i-1]+arr[i];
sum2[i]=sum2[i-1]+arr[i]*arr[i];
}
m=n-k;
if(m==0){
printf("0\n");
} else{
aver=sum[m]/m;
minm=sum2[m]+m*aver*aver-2*aver*sum[m];
for(int i=m+1;i<=n;i++){
aver=(sum[i]-sum[i-m])/m;
temp=sum2[i]-sum2[i-m]+m*aver*aver-2*aver*(sum[i]-sum[i-m]);
if(temp<minm){
minm=temp;
}
}
printf("%.10f\n",minm);
}
}
return 0;
}