【数据规模】
对于 40 % 40\% 40% 的数据,保证有 m ≤ n ≤ 10 m \le n \le 10 m≤n≤10, 2 ≤ m ≤ 6 2 \le m \le 6 2≤m≤6
对于 100 % 100\% 100% 的数据,保证有 m ≤ n ≤ 20 m \le n \le 20 m≤n≤20, 2 ≤ m ≤ 6 2 \le m \le 6 2≤m≤6
解法
模拟退火
首先考虑把均方差的式子划开,找出哪些项和分组的方案有关
先不管外面的根号,这显然和分组的方案无关,然后把里面那个式子拿出来,发现就是方差,然后按照方差的方式化简,可以发现和分组有关的方案只有每一组数的和的平方,而且这个东西,如果知道顺序,是可以直接dp的,所以模拟退火的主要任务就是求出合理的顺序,使得每一组数的和的平方尽量小.然后模拟退火交换两个数的相邻位置.
#include<bits/stdc++.h>
using namespace std;
const double delta=0.98;
inline int read(){
char c=getchar();int t=0,f=1;
while(!(isdigit(c))&&(c!=EOF)){if(c=='-')f=-1;c=getchar();}
while((isdigit(c))&&(c!=EOF)){t=(t<<3)+(t<<1)+(c^48);c=getchar();}
return t*f;
}
int n,m,a[25];
double b[25];
int f[25][25],s[25];
#define pf(x) ((x)*(x))
double calc(int a[]){
memset(f,0x3f,sizeof(f));
for(int i=1;i<=n;i++)s[i]=s[i-1]+a[i];
f[0][0]=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
for(int k=0;k<i;k++)
f[i][j]=min(f[i][j],f[k][j-1]+pf(s[i]-s[k]));
}
}
//printf("%d\n",f[n][m]);
return 1.0*f[n][m]-(double)pf(s[n])/m;
}
void SA(){
double T=1e6;
int dfn=0;
double ans=1e9,res;
int st=clock();
while(++dfn){
if(dfn%100==0)if(clock()-st>=0.7*CLOCKS_PER_SEC)break;
int x=rand()%n+1,y=rand()%n+1;
swap(a[x],a[y]);
res=calc(a);
//for(int i=1;i<=n;i++)printf("%d ",a[i]);
//puts("");
//printf("%.2lf\n",sqrt(res/m));
if(res<ans||exp(ans-res)/T>(double)rand()/RAND_MAX);
else swap(a[x],a[y]);
ans=min(ans,res);
T*=delta;
}
printf("%.2lf\n",sqrt(ans/m));
}
int main(){
n=read(),m=read();
for(int i=1;i<=n;i++){
a[i]=read();
}
srand(20200113);
SA();
return 0;
}
后续
可以发现我们这里的模拟退火并未怎么用到针对的这个函数的性质,所以可以直接随机化序列,也可以通过
// luogu-judger-enable-o2
#include <bits/stdc++.h>
#define re register
#define sqr(x) ((x)*(x))
using namespace std;
inline int read() {
int X=0,w=1; char c=getchar();
while (c<'0'||c>'9') { if (c=='-') w=-1; c=getchar(); }
while (c>='0'&&c<='9') X=(X<<3)+(X<<1)+c-'0',c=getchar();
return X*w;
}
int n,m,sum;
int a[30];
int x[10];
double ans=1000000000.0;
double ave;
inline void calc() {
memset(x,0,sizeof(x));
for (re int i=1;i<=n;i++) {
int p=1;
for (re int j=1;j<=m;j++)
if (x[j]<x[p]) p=j;
x[p]+=a[i];
}
double now=0;
for (re int i=1;i<=m;i++) now+=sqr(x[i]-ave);
now/=(double)m;
if (now<ans) ans=now;
}
int main() {
n=read(),m=read();
for (re int i=1;i<=n;i++) a[i]=read(),sum+=a[i];
ave=(double)sum/m;
for (re int i=1;i<=500000;i++) {
random_shuffle(a+1,a+n+1);
calc();
}
printf("%.2f\n",sqrt(ans));
return 0;
}