模拟退火摘要

OI界中有两大玄学算法,一为网络流,一为模拟退火——沃·兹基硕德


算法思想来源

在热力学上,退火现象指物体逐渐降温的物理现象,温度愈低,物体的能量状态会低;够低后,液体开始冷凝与结晶,在结晶状态时,系统的能量状态最低。大自然在缓慢降温(亦即,退火)时,可“找到”最低能量状态:结晶态。但是,如果过程过急过快,快速降温(亦称「淬炼」)时,会导致不是最低能态的非晶形。

那么,我们可不可以把找到最优解,与形成结晶态,这两个过程联系在一起呢?

于是,模拟退火诞生了。

模拟退火算法描述

若J(Y(i+1))>=J(Y(i))(即移动后得到更优解),则总是接受该移动

若J(Y(i+1))<J(Y(i))(即移动后的解比当前解要差),则以一定的概率接受移动,而且这个概率随着时间推移逐渐降低(逐渐降低才能趋向稳定)

根据热力学的原理,在温度为T时,出现能量差为dE的降温的概率为P(dE),表示为:P(dE) = exp( dE/(kT) )

其中k是一个常数,exp表示自然指数,dE为两个状态的能量差且dE<0。这条公式说白了就是:温度越高,出现一次能量差为dE的降温的概率就越大;温度越低,则出现降温的概率就越小。又由于dE总是小于0(否则就不叫退火了),因此dE/kT < 0 ,所以P(dE)的函数取值范围是(0,1) 。

若新状态优于当前状态则以一的概率接受新状态,否则以一定概率(这个概率写上面了)接受新状态。随着温度T的降低,P(dE)会逐渐降低。

模拟演示

一般格式

 1 const double jwl=...;const double Tmin=...;                    //降温率和截止温度 
 2 double ans,...;                                                //用来存当前解 
 3 double f(...){...}                                            //估价函数 
 4 void mnth(){double T=...                                    //设定初始温度 
 5     ...                                                     //随机产生一个状态 
 6     ans=f(...);                                                //计算估价函数 
 7     while(T>=Tmin){
 8         ...                                                    //随机产生新的状态 
 9         double newans=f(...);                                //计算新状态的估价函数 
10         double DE=newans-nowans;                            //新的估价函数值与全局估价函数值之差 
11         if(DE<0){...ans=newans;}                            //若新状态更优则一定接受新状态 
12         else if(exp(-DE/T)*RAND_MAX>rand()){...ans=newans;}    //否则以一定概率接受新状态 
13             T*=jwl;                                            //降温 
14         }
15 }
模拟退火一般格式

当然,实际操作的时候我们较少直接输出最终解, 而是选择在模拟退火的过程中单独维护一个解, 只在遇到更优解的时候将其更新, 增加正确率。

关于参数和调参

影响模拟退火性能的一个关键就是几个参数:

  • 温度T
  • 截止温度Tmin
  • 降温率jwl

一般降温率jwl与1的差减少一个数量级,时间消耗大约多10倍,T和Tmin变化一个数量级, 时间消耗不会变化很大。

至于调参么,比较麻烦的是温度和降温率。首先不必顾虑,都开大一点,先把最优解跑出来。如果发现经常陷入局部最优解的话考虑增大T和jwl, 如果发现最终精度不够的话考虑减小Tmin。
然后,手动二分吧,注意每个二分的值要多跑几遍,因为模拟退火有偶然性,一次跑出最优解不代表大部分时候都能。
不过因为有两个量,还不能直接二分,应该二分套二分比较合适。

时空复杂度

时间复杂度

O(???)

空间复杂度

O(???)

例题

BZOJ3680: 吊打XXX/洛谷P1337 [JSOI2004]平衡点 / 吊打XXX

AC代码

一开始在洛谷上AC后交到BZOJ上TLE,于是在BZOJ上改AC后交到洛谷上WA。。。

最后一怒之下重调参再交,终于两个OJ都AC了。。。

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 typedef long long ll;const int INF=0x7fffffff;const double jwl=0.995;const double Tmin=1e-8;
 4 const int N=10010;int n;double x[N],y[N],w[N];double mx,my,mans;
 5 template<class T>
 6 inline T read(T &n){
 7     n=0;int t=1;double x=10;char ch;
 8     for(ch=getchar();!isdigit(ch)&&ch!='-';ch=getchar());(ch=='-')?t=-1:n=ch-'0';
 9     for(ch=getchar();isdigit(ch);ch=getchar()) n=n*10+ch-'0';
10     if(ch=='.') for(ch=getchar();isdigit(ch);ch=getchar()) n+=(ch-'0')/x,x*=10;return (n*=t);
11 }double E(double nowx,double nowy){double sum=0;
12     for(int i=1;i<=n;++i){double delx=nowx-x[i];double dely=nowy-y[i];
13         sum+=(sqrt(delx*delx+dely*dely))*w[i];
14     }return sum;
15 }void mnth(){double T=10000;double nowx=mx,nowy=my;double nowans=mans;
16     while(T>=Tmin){double newx=nowx+(rand()%100*2-100)*T,newy=nowy+(rand()%100*2-100)*T;
17         double newans=E(newx,newy);double DE=newans-nowans;
18         if(DE<0){nowx=newx;nowy=newy;nowans=newans;if(nowans<mans){mx=nowx;my=nowy;mans=nowans;}}
19         else if(exp(-DE/T)*RAND_MAX>rand()){nowx=newx;nowy=newy;nowans=newans;}T*=jwl;
20     }
21 }int main(){read(n);for(int i=1;i<=n;++i){mx+=read(x[i]);my+=read(y[i]);read(w[i]);}mx/=n;my/=n;
22     mans=E(mx,my);mnth();printf("%.3f %.3f",mx,my);return 0;
23 }
吊打XXX

 

posted @ 2018-08-13 09:00  A星际穿越  阅读(285)  评论(0编辑  收藏  举报