模拟退火骗分
每次随机一个点,然后把它随机放入另外一个集合里,如果解更优就更新,如果不是更优,则根据当前温度随机判断是否更新,温度越高更新的几率越高,并不断降温,直到小于精度为止
多做几遍即可
代码如下:
#include<algorithm>
#include<cstring>
#include<ctype.h>
#include<cstdio>
#include<cmath>
#define eps 1e-4
#define INF 2147483647
#define N 10020
using namespace std;
const int root=20020605;
inline int read(){
int x=0,f=1;char c;
do c=getchar(),f=c=='-'?-1:f; while(!isdigit(c));
do x=(x<<3)+(x<<1)+c-'0',c=getchar(); while(isdigit(c));
return x*f;
}
int n,m;
int a[N],belong[N],sum[N];
double ans=1e30,ave;
inline double SA(){
double ans=0,T=10000;
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);///ans是当前的每组的和与平均数差的平方和
while(T>eps){
T*=0.9;///降温
int t=rand()%n+1,x=belong[t],y=0;///t:要拿走的数x:从哪个集合拿走y:放到哪里
if(T>1000){///如果温度太高,则说明数列不稳定,挑一个和比较小的集合把t放到那里
for(int i=1;i<=m;i++)
if(!y || sum[i]<sum[y])
y=i;
}
else y=rand()%m+1;///否则随机找一个集合放进去
if(x==y) continue;
double tmp=ans;
tmp-=(sum[x]-ave)*(sum[x]-ave);tmp-=(sum[y]-ave)*(sum[y]-ave);
sum[x]-=a[t];sum[y]+=a[t];
tmp+=(sum[x]-ave)*(sum[x]-ave);tmp+=(sum[y]-ave)*(sum[y]-ave);
if(tmp<=ans || rand()%10000<T)///如果解更优,或概率到那里的话就更新
belong[t]=y,ans=tmp;
else sum[x]+=a[t],sum[y]-=a[t];///否则不更新
}
return ans;
}
int main(){
srand(root);
n=read();m=read();
for(int i=1;i<=n;i++){
a[i]=read();
ave+=a[i];
}
ave=ave/(double)m;
for(int i=1;i<=10000;i++)///多做几遍,玄学东西
ans=min(SA(),ans);
printf("%.2lf",sqrt(ans/m));
return 0;
}