背景:
模拟退火大法好
.
.
.
...
...
题意:
给出
n
n
n个数,将其分成
m
m
m组,求每组和的均方差(标准差)的最小值。
思路:
先随机出每一个数属于哪一组。
模拟退火,每一次选择一个数将其放在权值和最小的一组。
代码:
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#define eps 1e-16
using namespace std;
int n,m;
double ave,ANS=2147483647;
int a[50],belong[50],sum[50];
int findmin()
{
double mi=2147483647;
int pos;
for(int i=1;i<=m;i++)
if(sum[i]<mi) mi=sum[i],pos=i;
return pos;
}
void fire()
{
double ans=0;
memset(sum,0,sizeof(sum));
for(int i=1;i<=n;i++)
{
belong[i]=rand()%m+1;
sum[belong[i]]+=a[i];
}
for(int i=1;i<=m;i++)
ans+=(sum[i]-ave)*(sum[i]-ave);
double t=200;
while(t>eps)
{
int x=rand()%n+1,min_pos=findmin();
double now=ans-(sum[belong[x]]-ave)*(sum[belong[x]]-ave)-(sum[min_pos]-ave)*(sum[min_pos]-ave);
sum[belong[x]]-=a[x],sum[min_pos]+=a[x];
now+=(sum[belong[x]]-ave)*(sum[belong[x]]-ave)+(sum[min_pos]-ave)*(sum[min_pos]-ave);
double delta=now-ans;
if(delta<0) ans=now,belong[x]=min_pos;
else if(exp(-delta/t)*RAND_MAX>=rand()) ans=now,belong[x]=min_pos;
else sum[belong[x]]+=a[x],sum[min_pos]-=a[x];
t*=0.996;
}
ANS=min(ANS,ans);
}
int main()
{
int op=0;
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
op+=a[i];
}
ave=(double)op/m;
for(int i=1;i<=100;i++)
fire();
printf("%.2lf",sqrt(ANS/m));
}