zoukankan      html  css  js  c++  java
  • 模拟退火

    模拟退火是常常用来解决最优化问题的算法 . 在 (mathrm{OI}) 竞赛中广泛应用 .

    虽然不知道正解是啥 , 但我给你退个几万遍火绝对不怂你 .

    算法梗概

    爬山算法 不同的是 , 爬山每次会找一个能上升的点 , 然后快速向那边爬 , 然后多随机几个初始点去爬 .

    退火 就是 后继状态虽然不一定比当前优秀 , 但我仍然有几率向那边走 .

    它是模拟热力学退火过程的一个神奇的算法 , 将我们 目标函数 作为 能量函数 最后使得能量越来越低 .

    后继状态比当前要优秀 , 那么直接继承这个后继状态 .

    高温的时候我们大概率选择不优秀的后继状态 , 低温的时候我们小概率选择不优秀的后继状态 , 因为越低温越稳定 .

    实现过程 : 初始高温 ( o) 温度缓慢下降 ( o) 终止在低温 (这时能量函数达到最小,目标函数最小)

    算法实现

    假设我们当前是使得最后的答案最小 .

    那么每次我们是否继承后继状态是分两种选择的 .

    我们假设本来状态的答案是 (bef) , 随机改变状态后的答案是 (res) , 当前温度为 (T) .

    1. (res < bef) , 直接继承 (res) .
    2. (res ge bef) , 我们以一定概率继承 就是 $$displaystyle e ^ {frac{-|res-bef|}{T}} in (0,1]$$ 也就是温度越高 继承概率越大 , 改变的越小 继承概率越大 . 反之则反 .

    然后最后经常会获得一个很优秀的最终状态 .

    代码实现

    (ans) 为全局的最优答案 .

    (Possible()) 返回 ((0,1]) 之间等概率随机的一个小数 . (Init()) 初始化 . (Calc()) 计算当前方案的答案 .

    (Change()) 改变当前的方案 , (Recover()) 恢复之前的方案 .

    有时候那个概率函数 (exp) 那里 , 容易写反 , 为了使得 (e^x le 1) 那么我们强制使得 (x le 0) 就行了 .

    这个参数 (DeltaT, eps, T) 是随便选择的 , 一般的话需要调参找到最优的参数 .

    const int lim = 1e4; const double eps = 1e-7; int ans = 1e9;
    inline double Possible() { return rand() * 1.0 / RAND_MAX; }
    inline void Simulate_Anneal() {
    	const double DeltaT = 0.99; 
    	int res = Init();
    	for (double T = 1e6; T > eps; T *= DeltaT) {
    		int bef = res; Change(); res = Calc();
    		if (!(res < bef || exp(-fabs(res - bef) / T) > Possible())) res = bef, Recover();
    		ans = min(ans, res);
    	}
    }
    

    例题

    BZOJ 3680 : 吊打XXX

    (n) 个洞 , 每个洞坐标为 ((x_i, y_i)) 用完全弹性的绳子下面挂一个质量为 (m_i) 的小球 .

    然后所有绳子连向同一个绳结 . 忽略 摩擦 和 能量损失 (机械能守恒) .

    球无限高 , 不会碰到地面 . ((1 le n le 1000))

    一开始莫名奇妙地想到了机械能守恒 , 然后列动能和重力势能的方程 , 最后可以动态分析每个点的速度 ?

    再来个动量守恒 ? 好吧 ... 那样就很毒瘤了 ...

    直接考虑共点力的平衡 , 对所有力进行正交分解 , 然后再合成到一起 . 然后如果最后合成力越小越优秀 .

    然后直接模拟退火 (爬山也行 , 因为单峰) 每次随机一个方向走 . 注意一开始跑多点 , 不然要找到最优解很慢 .

    我们可以一开始走温度 (T) 那么长的路 这样就行了 .

    挂一个 (Calc()) 看一下正交分解 ...

    #define sqr(x) ((x) * (x))
    
    inline double Calc(double x, double y) {
    	double sumx = 0.0, sumy = 0.0;
    	For (i, 1, n) {
    		double deltax = lis[i].x - x, deltay = lis[i].y - y;
    		double len = sqrt(sqr(deltax) + sqr(deltay));
    		if (fabs(len) <= eps) continue ;
    		sumx += lis[i].w * deltax / len;
    		sumy += lis[i].w * deltay / len;
    	}
    	return sqrt(sqr(sumx) + sqr(sumy));
    }
    

    BZOJ 2428: [HAOI2006]均分数据

    (n) 个正整数 (a_1 ... a_n) . 现在将他们分成 (m) 组 , 使得每组数值和均方差最小 . 即

    [displaystyle sigma = sqrt{frac{sum_{i=1}^n(x_i-overline{x})^2}{n}}, overline{x} = frac{sum _{i}^nx_i}{n} ]

    (sigma) 为均方差 , (overline{x}) 为各组数据和平均值 , (x_i) 为第 (i) 组数据的数值和 .

    $ m le n le 20, 2 le m le 6$

    这道题直接模拟退火就行了 , 似乎是模板题 ?

    每次随机选择一个数 , 然后把他拿出来 , 然后贪心地放入当前权值最小的那一组中去 .

    不然随便放的话就很找到的解就不优秀 . 基本上 (100) 次就可以退出最优解了 .

  • 相关阅读:
    mongodb的sql日志
    mysql – 在WHERE子句中使用substr的SELECT语句
    MySQL视图
    Linux简单查找log
    转 信号量与PV操作
    二进制小数及 IEEE 浮点表示
    转 :原码,反码,补码
    转:C# Delegate委托 1
    C#中Invoke的用法2
    C#中Invoke的用法1
  • 原文地址:https://www.cnblogs.com/zjp-shadow/p/9057871.html
Copyright © 2011-2022 走看看