模拟退火
模拟退火是一种玄学的随机算法。当问题的方案数量极大(甚至趋近于无穷)而且并不是一个单峰函数的时候,用模拟退火求解。
模拟转移的过程
退火指的是一种金属热处理工艺,将金属缓慢加热到一定的温度,保持足够的时间然后以十一速度冷却,降低硬度。由于退火的规律引入了很多随机的因素,得到最优解的概率会大大增加。
如果新状态的解更优则修改答案,否则以一定概率接受新状态。
设当前温度为TTT,新状态和已知状态(由已知状态通过随机的方式得到)之间的能量差为ΔE\Delta EΔE 。(能量差总大于0)则修改最优解的概率为
P(ΔE)={1 新状态更优e−ΔET 新状态更劣{\large P(\Delta E)=\left\{\begin{matrix} 1~~~~~~~~~~~~~~~~~新状态更优 \\ e^{- \frac{\Delta E}{T} ~~~~~~~~~~~~~~~~~{\large 新状态更劣} } \end{matrix}\right. }P(ΔE)={1 新状态更优e−TΔE 新状态更劣
模拟降温的过程
设:初始温度T0T_0T0,降温系数ddd,终止温度TkT_kTk。
其中,T0T_0T0是大正数,一般和数据范围有关。TkT_kTk是一个接近000的正数。
ddd是一个非常接近111但是小于111的正数。(根据我的做题经验,一般上需要不停的调ddd的大小进行控温,和机械公敌兰博的红温一样,一般上是0.9960.9960.996或者0.970.970.97)。这有关于查询的精度,有时候ddd的精度决定这题的分数,所以很重要。
首先让温度T=T0T=T_0T=T0,然后按照上述步骤进行一次转移尝试。
再让T=d∗TT=d*TT=d∗T。如果T≤TkT\le T_kT≤Tk,则模拟退火的过程结束。
为了使解更精确,我们通常不采用当前解作为答案,而是在模拟退火过程中维护遇到的所有最优解的值。通常可见到一道题跑了好几遍模拟退火。
//伪代码 -littlefools 2022.4.26
simulate_anneal(){
随机一个初始点;
for(double T=10000;T>1e-4;T=T*0.997){
在当前点周围随机一个点;
delta=function(new)-function(now);
if() 跳到新点;
}
}
如何控时?
为了不让我们跑了好多遍的模拟退火变成TLETLETLE丢掉所有的分数,我们必须进行控时。在C++中有一个clock()clock()clock()函数,可以返回程序的运行时间。
while((double)clock()/CLOCKS_PER_SEC<MAX_TIME) simulate_anneal();
这样子就会一直跑模拟退火直到它的用时即将超出时间限制。一般都是1s1s1s,至于能不能找到就看RPRPRP了。
如何等概率操作?
double rand(double l,double r){
return (double)rand()/RAND_MAX*(r-l)+l;
//rand()/RAND_MAX 是在0-1范围内取一个概率。
//*(r-l)+l 则可以得到[l,r]区间内等概率的一个点。
}
例题:[JSOI2004]费马点
有nnn个重物,每个重物系在一条足够长的绳子上。每条绳子自上而下穿过桌面上的洞,然后系在一起。图中XXX处就是公共的绳结。假设绳子是完全弹性的(不会造成能量损失),桌子足够高(因而重物不会垂到地上),且忽略所有的摩擦。问绳结X最终平衡于何处。
文件的第一行为一个正整数n(1≤n≤1000)n(1≤n≤1000)n(1≤n≤1000),表示重物和洞的数目。接下来的nnn行,每行是333个整数:Xi.Yi.WiXi.Yi.WiXi.Yi.Wi,分别表示第iii个洞的坐标以及第iii个重物的重量。(−10000≤x,y≤10000,0<w≤1000-10000≤x,y≤10000, 0<w≤1000−10000≤x,y≤10000,0<w≤1000 ) 。输出最终平衡状态的(x,y)(x,y)(x,y)坐标。保留两位小数。
#include <bits/stdc++.h>
using namespace std;
int n,x[1000000],y[1000000],w[1000000];
double ansx,ansy,answ;
double energy(double xx,double yy){
double r=0,ddx,ddy;
for (int i=1;i<=n;i++){
ddx=xx-x[i]; ddy=yy-y[i];
r+=sqrt(ddx*ddx+ddy*ddy)*w[i];
}
return r;
}
void anneal(){
for (double t=3000;t>1e-15;t*=0.997){//大于一个极小值
double dx=ansx+(rand()*2-RAND_MAX)*t,dy=ansy+(rand()*2-RAND_MAX)*t;
double ew=energy(dx,dy),delta=ew-answ;
if (delta<0){ansx=dx;ansy=dy;answ=ew;} //此答案更优
else if(exp(-delta/t)*RAND_MAX>rand()){ansx=dx;ansy=dy;}//否则以一定概率接受
t*=0.996;//徐徐降温
}
}
int main() {
cin>>n;
for (int i=1;i<=n;i++){
scanf("%d%d%d",&x[i],&y[i],&w[i]);ansx+=x[i];ansy+=y[i];
}
ansx/=n;ansy/=n;answ=energy(ansx,ansy);
for(int i=1;i<=5;i++) anneal();//需要跑多次模拟退火确定答案。
printf("%.3lf %.3lf\n",ansx,ansy);
return 0;
}

模拟退火算法是一种用于解决复杂优化问题的随机搜索策略,尤其在方案数量极大时。文章介绍了模拟转移过程,其中新状态以一定概率被接受,即使它可能导致解的恶化。此外,讲解了模拟降温过程,通过不断降低温度来逐步逼近最优解。控时和等概率操作也是关键点,以确保算法在限定时间内有效运行。最后,通过[JSOI2004]费马点问题举例说明了算法的应用。
485

被折叠的 条评论
为什么被折叠?



