zoukankan      html  css  js  c++  java
  • [HAOI2006]均分数据 模拟退火

    模拟退火 系列

    example 1 [HAOI2006]均分数据 

    https://www.luogu.org/problem/P2503

    【题意】

      把n个数以分为m组,计算每一组的和,求得到的这m个数的方差。由于分法是任意的,我们要求这些方差中的最小值

    【思路】

      首先给定一个序列,计算当前状态的最小方差的方法是,按照顺序加入,每次将数加入和最小的一个集合,最后计算方差。

    那么改变答案的方法就是改变序列的顺序。这里我用到的是交换其中的两个数,如果答案变小了就更新,否则有一定概率保留,一定概率换回去

    【代码】

    #include<cmath>
    #include<ctime>
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<algorithm>
    #define pf(x) ((x)*(x))
    using namespace std;
    typedef double real;
    const int N=30;
    real avg,now,best,sum,a[N],num[N],no[N],be[N];
    int n,m;
    inline real deal(){
        real fz=0;
        memset(num,0,sizeof num);
        for(int i=1,p=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                if(num[j]<num[p]) p=j;
            }
            num[p]+=no[i];
        }
        for(int i=1;i<=m;i++) fz+=pf(num[i]-avg);
        return sqrt(real(fz)/real(m));
    
    }
    int main(){
        srand(time(0));
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++) scanf("%lf",a+i),no[i]=be[i]=a[i],sum+=a[i];
        avg=real(sum)/real(m);
        best=deal();
        for(int it=30;it--;){
            now=best;
            for(int i=1;i<=n;i++) no[i]=be[i];
            for(real T=1e5;T>=1e-14;T*=0.97){
                int x=rand()%n+1,y=rand()%n+1;
                for(;x==y;y=rand()%n+1);
                swap(no[x],no[y]);
                real tmp=deal(),E=tmp-now;
                if(tmp<best){
                    best=tmp;
                    for(int j=1;j<=n;j++) be[j]=no[j];
                }
                if(tmp<now||E/T<real(rand())/real(RAND_MAX))
                    now=tmp;
                else 
                    swap(no[x],no[y]);
            }
        }
        printf("%.2lf",best);
        return 0;
    }

    经典模拟退火

    模拟退火的思想在于,如果一个移动会使答案变得更优,我们就接受这个移动;否则我们以一定的概率接受这个移动。

    听起来很玄学。根据物理的那套理论,我们定义两个东西:
    - 温度$(T)$。它随着时间推移而逐渐降低。
    - 增量$(E)$。它描述一次移动获得的好处。从$x$移动到$x'$的增量定义为$f(x')-f(x)$,增量越大,往$x'$移动的优势越大。

    在模拟退火中,如果增量大于$0$,则直接接受这次移动;否则按下面的概率接受移动:

    $$P = exp(frac{E}{T})$$

    听起来十分的玄学。然而它竟然可以得出精度比较好的解。伪代码如下:

    T=100.0;               //初始温度
    
    for(int i=0;i<100;i++)      //控制迭代次数
    {
        tar=getPos();           //在x的周围选一个点
        E=f(tar)-f(x);
    
        if(E > eps) x=tar;      //直接移动
        else if(exp(E/T) > random(0,1)) x=tar;     //接受移动
    
        T=T*0.99;               //降温
    }
    
  • 相关阅读:
    绕口令系列 1
    毕业论文排版
    使用matlab表示“段数不确定”的分段函数
    [转]C/C++关于全局变量和局部变量初始化与不初始化的区别
    [转]基于Protel DXP软件的PCB高级编辑技巧大全
    冒泡排序及其优化
    gcc编译器参数
    [转]跟我一起写Makefile系列
    实例说明optimize table在优化mysql时很重要
    log4php0.9的详细配备实例说明
  • 原文地址:https://www.cnblogs.com/shenben/p/11332985.html
Copyright © 2011-2022 走看看