模拟退火详解&P1433题解
前排提示:LZ是个菜比,有可能有讲的不对的地方,请在评论区指出qwq
0.基本思想
模拟退火其实没有那么高大上。说白了就是初始化一个“温度”。每次随机乱选一个方案,如果比以前的方案优那么就要,否则就以一定的概率要或者不要。当前方案越狗屎就越不想要,“温度”越低越不想要。然后把温度降低一些,反复循环,直到温度为0为止。
1.照本宣科 实现
Fuck CCF(小声
呃,就以 臭名昭著 著名的TSP问题举例子吧。
什么?你不知道TSP?这个就是->点我。
其实正解是搜索,但是\(O(n!)\)的时间复杂度实在伤不起(除了像本题一样\(n\le15\)),所以考虑模拟退火。
首先,初始化一个初始”温度“。越高越好,但是过高会让程序变慢,至于为什么以后再说。
const double T0=1e5/*初始温度*/,T_end=1e-4/*结束温度(由于非常接近0可以看作0)*/;
void SA(){
    double T=T0;//当前温度
    while(T>T_end){//对应”反复循环,直到温度为0为止。“这句话
    }
}
然后胡乱生成一个解:
double calc(){//意思是查询当前解的代价,具体到问题里就是按照当前顺序访问要走多远
    double ret=0.0;
    for(int i=1;i<=n;i++){
        ret+=g[ans[i-1]][ans[i]];//g数组的意思是从一个点到另一个点要走多少
    }
    return ret;
}
int random_disp(int l,int r){//意思是在区间[l,r]内随机生成一个数
    srand(time(NULL));
    static std::mt19937 random_engine(rand());
    if(l>r)swap(l,r);
    uniform_int_distribution<int> u(l,r);
    return u(random_engine);
}
const double T0=1e5/*初始温度*/,T_end=1e-4/*结束温度(由于非常接近0可以看作0)*/;
void SA(){
    double T=T0;//当前温度
    while(T>T_end){//对应”反复循环,直到温度为0为止。“这句话
        int u=random_disp(1,n),v=random_disp(1,n);
        swap(ans[u],ans[v]);
        double new_sol=calc();//随机交换两个数,就相当于乱生成一个
        //ans数组的意义是访问的顺序
    }
}
判断是否要这个解:
inline bool CBP(double x){//Choose by probability.
                          //以概率x返回 true或者false
    if(x>=1.0)return true;
    if(x<=0.0)return false;
    srand(time(NULL));
    static std::mt19937 random_engine(rand());
    uniform_real_distribution<double> u(0.0,1.0);
    return u(random_engine)<=x;
}
double calc(){//意思是查询当前解的代价,具体到问题里就是按照当前顺序访问要走多远
    double ret=0.0;
    for(int i=1;i<=n;i++){
        ret+=g[ans[i-1]][ans[i]];//g数组的意思是从一个点到另一个点要走多少
    }
    return ret;
}
int random_disp(int l,int r){//意思是在区间[l,r]内随机生成一个数
    srand(time(NULL));
    static std::mt19937 random_engine(rand());
    if(l>r)swap(l,r);
    uniform_int_distribution<int> u(l,r);
    return u(random_engine);
}
const double T0=1e5/*初始温度*/,T_end=1e-4/*结束温度(由于非常接近0可以看作0)*/;
void SA(){
    double T=T0;//当前温度
    while(T>T_end){//对应”反复循环,直到温度为0为止。“这句话
        int u=random_disp(1,n),v=random_disp(1,n);
        swap(ans[u],ans[v]);
        double new_sol=calc();//随机交换两个数,就相当于乱生成一个
        //ans数组的意义是访问的顺序
        if(new_sol<old_sol){//如果撞到狗屎运,随机乱搞一个都比以前的解好
            old_sol=new_sol;//那么更新
        }else if(CBP(exp(double(old_sol-new_sol)/T))){//否则以概率决定是否更新
            old_sol=new_sol;
        }else{
            swap(ans[u],ans[v]);//换回来,交换两次等于没换
        }
    }
}
等等,exp(double(old_sol-new_sol)/T)是什么意思?
这个我当初也蒙了半天(我太蔡了),尽量讲的明白一点
先把它翻译成数学语言:
\]
再翻译成人话:
\(e\) (是个常数,大约是2.7) 的 (以前解 - 当前解 )除以当前温度次方
(以前解 - 当前解 ),也就是\(\Delta f\),一定是个负数,为什么看看代码就知道了。
那么,\(\Delta f\)越小(也就是绝对值越大),也就是当前解越狗屎,\(\frac{\Delta f}{T}\)就越小。当\(T\)越小,也就是温度越小,\(\frac{\Delta f}{T}\)的绝对值也就越大,\(\frac{\Delta f}{T}\)也就越小。\(\frac{\Delta f}{T}\)越小,\(e^{\frac{\Delta f}{T}}\)也就越小(但一定大于0),正好对应了”当前方案越狗屎就越不想要,“温度”越低越不想要。“这句话。
^通读三遍再往下看
降低温度并记录遇到的最优解:
inline bool CBP(double x){//Choose by probability.
                         //以概率x返回 true或者false
   if(x>=1.0)return true;
   if(x<=0.0)return false;
   srand(time(NULL));
   static std::mt19937 random_engine(rand());
   uniform_real_distribution<double> u(0.0,1.0);
   return u(random_engine)<=x;
}
double calc(){//意思是查询当前解的代价,具体到问题里就是按照当前顺序访问要走多远
   double ret=0.0;
   for(int i=1;i<=n;i++){
       ret+=g[ans[i-1]][ans[i]];//g数组的意思是从一个点到另一个点要走多少
   }
   return ret;
}
int random_disp(int l,int r){//意思是在区间[l,r]内随机生成一个数
   srand(time(NULL));
   static std::mt19937 random_engine(rand());
   if(l>r)swap(l,r);
   uniform_int_distribution<int> u(l,r);
   return u(random_engine);
}
const double T0=1e5/*初始温度*/,T_end=1e-4/*结束温度(由于非常接近0可以看作0)*/;
void SA(){
   double T=T0;//当前温度
   while(T>T_end){//对应”反复循环,直到温度为0为止。“这句话
       int u=random_disp(1,n),v=random_disp(1,n);
       swap(ans[u],ans[v]);
       double new_sol=calc();//随机交换两个数,就相当于乱生成一个
       //ans数组的意义是访问的顺序
       if(new_sol<old_sol){//如果撞到狗屎运,随机乱搞一个都比以前的解好
           old_sol=new_sol;//那么更新
       }else if(CBP(exp(double(old_sol-new_sol)/T))){//否则以概率决定是否更新
           old_sol=new_sol;
       }else{
           swap(ans[u],ans[v]);//换回来,交换两次等于没换
       }
       ans_val=min(ans_val,old_sol);
       ans_val=min(ans_val,new_sol);//记录最优解
       T*=0.997;//缓缓降低
   }
}
然后,不停循环,直到温度为0为止。
Code:
#include <bits/stdc++.h>
using namespace std;
#define MAXN 20
int n,ans[MAXN],st=clock();
double g[MAXN][MAXN],x[MAXN],y[MAXN],ans_val=1e10;
double euc_dis(double x1,double y1,double x2,double y2){
   return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}
inline bool CBP(double x){//Choose by probability
   if(x>=1.0)return true;
   if(x<=0.0)return false;
   srand(time(NULL));
   static std::mt19937 random_engine(rand());
   uniform_real_distribution<double> u(0.0,1.0);
   return u(random_engine)<=x;
}
int random_disp(int l,int r){
   srand(time(NULL));
   static std::mt19937 random_engine(rand());
   if(l>r)swap(l,r);
   uniform_int_distribution<int> u(l,r);
   return u(random_engine);
}
double calc(){
   double ret=0.0;
   for(int i=1;i<=n;i++){
       ret+=g[ans[i-1]][ans[i]];
   }
   return ret;
}
const double T0=1e5,T_end=1e-4,DT=0.997;
void SA(){
   double T=T0,old_sol=calc();
   while(T>T_end){
       int u=random_disp(1,n),v=random_disp(1,n);
       swap(ans[u],ans[v]);
       double new_sol=calc();
       if(new_sol<old_sol){
           old_sol=new_sol;
       }else if(CBP(exp(double(old_sol-new_sol)/T))){
           old_sol=new_sol;
       }else{
           swap(ans[u],ans[v]);
       }
       ans_val=min(ans_val,old_sol);
       ans_val=min(ans_val,new_sol);
       T*=DT;
   }
}
int main(){
   srand(time(NULL));
   scanf("%d",&n);
   for(int i=1;i<=n;i++){
       scanf("%lf %lf",x+i,y+i);
   }
   for(int i=0;i<=n;i++){
       for(int j=0;j<=n;j++){
           g[i][j]=euc_dis(x[i],y[i],x[j],y[j]);
       }
   }
   for(int i=1;i<=n;i++){
       ans[i]=i;
   }
   while(clock()-st<0.95*CLOCKS_PER_SEC){//只要还没超时就不停退火
       SA();
   }
   printf("%.2lf\n",ans_val);
   return 0;
}
以上代码能够ACP1433,也就是例题。
3.一些注意事项
- 由于模拟退火是个概率算法,所以除非你想不出正解最好不要用。
 - 由于模拟退火是个概率算法,所以最好多跑几遍。
 - 由于模拟退火是个概率算法,所以要仔细调整几个参数——初始温度、结束温度、变化率。
 - 由于模拟退火是个概率算法,所以时间复杂度是\(O(玄学)\)。初始温度越高,温度变化率越接近1,跑得越慢,也越精确。
 - 由于模拟退火是个概率算法,所以LZ想不出来怎么继续队形了qwq。
 - 由于模拟退火是个概率算法,所以能给LZ点一个赞吗qwq
 
完结撒花~
完结撒CCF~
模拟退火详解&P1433题解的更多相关文章
- KMP算法详解&&P3375 【模板】KMP字符串匹配题解
		
KMP算法详解: KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt(雾)提出的. 对于字符串匹配问题(such as 问你在abababb中有多少个 ...
 - 高斯消元法(Gauss Elimination)【超详解&模板】
		
高斯消元法,是线性代数中的一个算法,可用来求解线性方程组,并可以求出矩阵的秩,以及求出可逆方阵的逆矩阵.高斯消元法的原理是:若用初等行变换将增广矩阵 化为 ,则AX = B与CX = D是同解方程组. ...
 - c语言贪吃蛇详解3.让蛇动起来
		
c语言贪吃蛇详解3.让蛇动起来 前几天的实验室培训课后作业我布置了贪吃蛇,今天有时间就来写一下题解.我将分几步来教大家写一个贪吃蛇小游戏.由于大家c语言未学完,这个教程只涉及数组和函数等知识点. 上次 ...
 - c语言贪吃蛇详解-2.画出蛇
		
c语言贪吃蛇详解-2.画出蛇 前几天的实验室培训课后作业我布置了贪吃蛇,今天有时间就来写一下题解.我将分几步来教大家写一个贪吃蛇小游戏.由于大家c语言未学完,这个教程只涉及数组和函数等知识点. 蛇的身 ...
 - c语言贪吃蛇详解1.画出地图
		
c语言贪吃蛇详解-1.画出地图 前几天的实验室培训课后作业我布置了贪吃蛇,今天有时间就来写一下题解.我将分几步来教大家写一个贪吃蛇小游戏.由于大家c语言未学完,这个教程只涉及数组和函数等知识点. 首先 ...
 - c语言贪吃蛇详解4.食物的投放与蛇的变长
		
c语言贪吃蛇详解4.食物的投放与蛇的变长 前几天的实验室培训课后作业我布置了贪吃蛇,今天有时间就来写一下题解.我将分几步来教大家写一个贪吃蛇小游戏.由于大家c语言未学完,这个教程只涉及数组和函数等知识 ...
 - HDU 1024 Max Sum Plus Plus【动态规划求最大M子段和详解 】
		
Max Sum Plus Plus Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others ...
 - BSGS(Baby Steps,Giant Steps)算法详解
		
BSGS(Baby Steps,Giant Steps)算法详解 简介: 此算法用于求解 Ax≡B(mod C): 由费马小定理可知: x可以在O(C)的时间内求解: 在x=c之后又会循环: 而BS ...
 - 【P2158】仪仗队&欧拉函数详解
		
来一道数论题吧. 这个题一眼看上去思路明确,应该是数论,但是推导公式的时候却出了问题,根本看不出来有什么规律.看了马佬题解明白了这么个规律貌似叫做欧拉函数,于是就去百度学习了一下这东西. 欧拉函数的含 ...
 
随机推荐
- 再见了Antirez永远的Redis之神
			
其实antirez(Redis作者)退出Redis维护一发布我就在很多咨询网站上面看到了,当时也没太多感慨. 今天比较有空想去看看霉霉Twitter的,然后看到了antirez,我就又一次回顾了他的退 ...
 - Python os.ftruncate() 方法
			
概述 os.ftruncate() 裁剪文件描述符fd对应的文件, 它最大不能超过文件大小.高佣联盟 www.cgewang.com Unix, Windows上可用. 语法 ftruncate()方 ...
 - PHP password_needs_rehash() 函数
			
password_hash() 函数用于检测散列值是否匹配指定的选项. PHP 版本要求: PHP 5 >= 5.5.0, PHP 7高佣联盟 www.cgewang.com 语法 bool p ...
 - PHP 获取图像宽度与高度
			
PHP 获取图像宽度函数:imagesx() imagesx() 函数用于获取图像的宽度,单位为像素,返回值为整型.高佣联盟 www.cgewang.com 语法: int imagesx( reso ...
 - Ynoi专练
			
为了练习分块 莫队 bitset黑科技 我会写几道Ynoi 放到这里. bitset 每一位占1bit int 每一位占 4 bitye bool占1 bitye long long 8bitye L ...
 - Python高手是怎样炼成的!
			
很多想从事python行业的朋友都会问到,零基础如何自学成为Python高手?根据小北多年教育的经验,我总结了几个小建议,想看干货的请看下文! 如何克服入门难问题? 其实小北觉得,最好的方法就是和一群 ...
 - 基于Qt实现的TCP端口数据转发服务器
			
对于Qt,比较喜欢qt的sdk框架,我也是用于做一些工作中用到的工具软件,基于qt的sdk做起来也比较快: 一.概述 今天要说的这个tcp端口转发服务器,主要是用于将监听端口的数据转发到另外一个服务器 ...
 - 精讲RestTemplate第3篇-GET请求使用方法详解
			
本文是精讲RestTemplate第3篇,前篇的blog访问地址如下: 精讲RestTemplate第1篇-在Spring或非Spring环境下如何使用 精讲RestTemplate第2篇-多种底层H ...
 - 再见HTML ! 用纯Python就能写一个漂亮的网页
			
我们在写一个网站或者一个网页界面的时候,需要学习很多东西,对小白来说很困难!比如我要做一个简单的网页交互: 很多人学习python,不知道从何学起.很多人学习python,掌握了基本语法过后,不知道在 ...
 - Java—增强for循环与for循环的区别/泛型通配符/LinkedList集合
			
增强for循环 增强for循环是JDK1.5以后出来的一个高级for循环,专门用来遍历数组和集合的. 它的内部原理其实是个Iterator迭代器,所以在遍历的过程中,不能对集合中的元素进行增删操作. ...