新知识get:模拟退火
设置一个温度T,T越小越不随机
然后做个几万遍就可以了。
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
double ans=2147483647,average=0;
int n,m,a[10005],sum[10005],zu[10005];
void Fire(){
double T=10000,nowans=0;
memset(sum,0,sizeof(sum));
for(int i=1;i<=n;i++){
zu[i]=rand()%m+1;
sum[zu[i]]+=a[i];
}
for(int i=1;i<=m;i++){
nowans+=(sum[i]-average)*(sum[i]-average);
}
while(T>0.1){
T*=0.9;
int Nx,Ny,R=rand()%n+1;
Nx=zu[R];
if(T>500){
int sa=2147483647,sb;
for(int i=1;i<=m;i++){
if(sa>sum[i]){
sa=sum[i];
sb=i;
}
}
Ny=sb;
}
else Ny=rand()%m+1;
if(Nx==Ny)continue;
double lastans=nowans;
nowans-=(sum[Nx]-average)*(sum[Nx]-average);
nowans-=(sum[Ny]-average)*(sum[Ny]-average);
sum[Nx]-=a[R];
sum[Ny]+=a[R];
nowans+=(sum[Nx]-average)*(sum[Nx]-average);
nowans+=(sum[Ny]-average)*(sum[Ny]-average);
if(nowans<=lastans){zu[R]=Ny;}
else {
if(rand()%10000>T){
sum[Nx]+=a[R];
sum[Ny]-=a[R];
nowans=lastans;
}
else {
zu[R]=Ny;
}
}
}
ans=min(nowans,ans);
}
int main(){
srand(19990720);
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
average+=a[i];
}
average/=m;
for(int i=1;i<=10000;i++){
Fire();
}
printf("%.2lf\n",sqrt(ans/m));
return 0;
}