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

探讨了使用模拟退火算法解决N个数分成M组的问题,目标是最小化均方差。通过随机初始解、迭代改进解及温度冷却策略实现优化。介绍了核心函数如F()用于计算均方差、move()进行状态转移、failed()判断是否接受新解等。

题意: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;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值