题目链接
http://www.lydsy.com/JudgeOnline/problem.php?id=2428
思路
模拟退火乱搞
首先随机设定每个元素所在的组,然后求出初始的方差,然后开始退火,每次退火时首先随机选一个元素
t
,并得到它所在的组
显然这样的概率性算法,只做1次是不够的,因为开始时就是在随机地确定每个元素所在的组,因此要做很多次,经我实验,最保险是做[10000,20000]次,不过速度比较慢,大概1000ms~1500ms不等,一般的种子做5000次也可以,最快的话可以用一个1e6~1e7的大质数充当随机种子,最少要做2000次,加上内联优化只需200+ms,直接刷到这个题的榜的第一面23333
代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <cmath>
#define MAXN 10000
using namespace std;
double minans=1e30,avenue=0; //avenue=平均值
int n,m,a[MAXN];
int sum[MAXN]; //sum[i]=第i组的(元素-所有数据平均值)^2和
int belong[MAXN]; //belong[i]=元素i所属的组的编号
void SA()
{
memset(sum,0,sizeof(sum));
for(int i=1;i<=n;i++)
{
belong[i]=rand()%m+1;
sum[belong[i]]+=a[i];
}
double ans=0; //ans=当前的每组(元素-平均值)^2之和
for(int i=1;i<=m;i++)
ans+=(sum[i]-avenue)*(sum[i]-avenue);
double T=10000; //退火温度
while(T>0.1)
{
T*=0.9;
int t=rand()%n+1,x=belong[t],y; //被换的元素是t,t在组x中,换到组y去
if(T>500) //温度很高时,此时波动很大,y取当前元素之和最小的那一组,这样准确一些
y=min_element(sum+1,sum+m+1)-sum;
else //温度低时,y随机选,让答案渐渐更优
y=rand()%m+1;
if(x==y) continue; //RP不好时x可能和y相同,那么跳过本次操作,重新选x和y
double tmp=ans; //tmp=之前的ans值
//更新ans值
ans-=(sum[x]-avenue)*(sum[x]-avenue);
ans-=(sum[y]-avenue)*(sum[y]-avenue);
sum[x]-=a[t],sum[y]+=a[t];
ans+=(sum[x]-avenue)*(sum[x]-avenue);
ans+=(sum[y]-avenue)*(sum[y]-avenue);
if(ans<=tmp) //新的解更优,就选择新的解
belong[t]=y; //标记t现在在y组里
else if(rand()%10000>T) //否则如果随机后决定移动到新解处,就移动,但是保持原来的解不变
{
sum[x]+=a[t],sum[y]-=a[t]; //随机后决定不移动
ans=tmp;
}
else belong[t]=y; //随机后决定移动
}
if(ans<minans) minans=ans; //更新最小答案
}
int main()
{
srand(19990720);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&a[i]),avenue+=a[i];
avenue/=(double)m;
for(int i=1;i<=10000;i++) SA();
printf("%.2lf\n",sqrt(minans/m));
return 0;
}