您的位置:首页 > 其它

BZOJ2428: [HAOI2006]均分数据 模拟退火

2017-01-03 19:41 471 查看
题意:N个数分成M组,要求均方差最小

N<=20,M<=6

随机一种分法,然后模拟退火,每次把一个数随机分到另外一组,若rand(0,1)< exp(dt/T)则判定为成功。然而WA了很久,最后知道温度高的时候需要贪心把它放入当前和最小的一组。。。还是不行,于是改成了从头模拟退火20次才终于过了,慢得垫底,真玄学

#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
using namespace std;
int n,m;
int w[20],a[20][21],t[20],sum[20],pos[20];
inline double F()
{
int asum=0;
for(int i=0;i<m;++i)
{
sum[i]=0;
for(int j=1;j<=t[i];++j)
sum[i]+=w[a[i][j]];
asum+=sum[i];
}
double dx=floor(asum)/m,res=0;
for(int i=0;i<m;++i)
res+=(sum[i]-dx)*(sum[i]-dx);
return res;
}
inline int move(int x,int p)
{
int b=pos[x];
for(int i=t[b]--;;--i)
{
if(a[b][i]==x)
{
for(;i<=t[b];++i)
a[b][i]=a[b][i+1];
break;
}
}
a[p][++t[pos[x]=p]]=x;
return b;
}
inline bool failed(const double &dt,const double &T)
{
return dt<0.0&&exp(dt/T)<=floor(rand())/RAND_MAX;
}
inline void random_shuffle()
{
int aim;
for(int i=0;i<n;++i)
{
pos[i]=aim=rand()%m;
a[aim][++t[aim]]=i;
}
}
inline int greedy_choice()
{
int minn=0x7fffffff,res;
for(int i=0;i<m;++i)
{
sum[i]=0;
for(int j=1;j<=t[i];++j)
sum[i]+=w[a[i][j]];
if(sum[i]<minn) minn=sum[i],res=i;
}
return res;
}
inline double fire()
{
memset(t,0,sizeof t);
double T=1e16,ans,dt,res;
int ele,aim,org;
random_shuffle();
res=ans=F();
while(T>1e-10)
{
ele=rand()%n;
if(T>1e2) aim=greedy_choice();
else aim=rand()%m;
org=move(ele,aim);
dt=ans-F();
if(ans-dt<res) res=ans-dt;
if(failed(dt,T)) move(ele,org);
else ans-=dt;
T*=0.999;
}
return res;
}
inline void min(double &a,const double &b)
{
if(b<a) a=b;
}
int main()
{
srand(114514);
scanf("%d%d",&n,&m);
for(int i=0;i<n;++i)
scanf("%d",w+i);
double ans=1e10;
for(int i=1;i<=20;++i)
min(ans,sqrt(fire()/m));
printf("%.2lf\n",ans);
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: