模拟退火

Posted zjp_shadow

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了模拟退火相关的知识,希望对你有一定的参考价值。

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

虽然不能做到最优 , 但我给你退个几万遍火绝对不怂你 .

  • 算法梗概 :

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

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

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

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

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

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

  • 算法实现 :

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

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

我们假设本来状态的答案是 \(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\) 次就可以退出最优解了 .

以上是关于模拟退火的主要内容,如果未能解决你的问题,请参考以下文章

Matlab 模拟退火算法模型代码

loj#2076. 「JSOI2016」炸弹攻击 模拟退火

C++2018华为软挑:模拟退火+贪心FF解决装箱问题

模拟退火板子

模拟退火入门——求解TSP和洛谷P2210

模拟退火解TSP问题MATLAB代码