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

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

#include
#include
#include
#include
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;i0;
        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;ireturn 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;iinline int greedy_choice()
{
    int minn=0x7fffffff,res;
    for(int i=0;i0;
        for(int j=1;j<=t[i];++j)
        sum[i]+=w[a[i][j]];
        if(sum[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-dtif(failed(dt,T)) move(ele,org);
        else ans-=dt;
        T*=0.999;
    }
    return res;
}
inline void min(double &a,const double &b)
{
    if(bint main()
{
    srand(114514);
    scanf("%d%d",&n,&m);
    for(int i=0;iscanf("%d",w+i);
    double ans=1e10;
    for(int i=1;i<=20;++i)
    min(ans,sqrt(fire()/m));
    printf("%.2lf\n",ans);
    return 0;
}

你可能感兴趣的:(模拟退火,BZOJ做题纪录)