【总结】模拟退火算法(随机化)

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

模拟退火

模拟退火是一种玄学的随机算法。当问题的方案数量极大(甚至趋近于无穷)而且并不是一个单峰函数的时候,用模拟退火求解。

模拟转移的过程

退火指的是一种金属热处理工艺,将金属缓慢加热到一定的温度,保持足够的时间然后以十一速度冷却,降低硬度。由于退火的规律引入了很多随机的因素,得到最优解的概率会大大增加。

如果新状态的解更优则修改答案,否则以一定概率接受新状态。

设当前温度为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                 eTΔ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=dT。如果T≤TkT\le T_kTTk,则模拟退火的过程结束。

为了使解更精确,我们通常不采用当前解作为答案,而是在模拟退火过程中维护遇到的所有最优解的值。通常可见到一道题跑了好几遍模拟退火。

//伪代码 -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)n1n1000,表示重物和洞的数目。接下来的nnn行,每行是333个整数:Xi.Yi.WiXi.Yi.WiXi.Yi.Wi,分别表示第iii个洞的坐标以及第iii个重物的重量。(−10000≤x,y≤10000,0<w≤1000-10000≤x,y≤10000, 0<w≤100010000x,y10000,0<w1000 ) 。输出最终平衡状态的(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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值