本文最后更新于:2023年9月17日 上午 11:21

概述
简单说,模拟退火是一种随机化算法,用于求函数的极值。当一个问题的方案数量极大(甚至是无穷的)而且不是一个单峰函数时,我们常使用模拟退火求解。
它与爬山算法最大的不同是,在寻找到一个局部最优解时,赋予了它一个跳出去的概率,也就有更大的机会能找到全局最优解。
在 OI
领域,对应的,每次随机出一个新解,如果这个解更优,则接受它,否则以一个与温度和与最优解的差相关的概率接受它。
相关参数
初始温度:\(T\_0\)
结束温度:\(T\_s\)
降温系数:\(\Delta t\)
这样每次温度就是上次的温度乘上\(\Delta
t\)
能量差:\(\Delta
E=f(t_{new})−f(t_{now})\),即新点的能量减去当前的能量(能量也就是函数值)
接受概率:\(P(\Delta
E)=e^{\frac{−ΔE}{t}}\),t为当前温度,这样保证当能量差小于0时,概率P是大于1的,也就是必然接受,当能量差大于0时,能量差越大越不容易接受,t越大越容易接受(这里能量差需要具体问题具体分析,因为是退火,所以能量是越小越好)
模板
AcWing 3167. 星星还是树
二维平面有若干点,寻找一个点到所有点的距离之和最短,该点可以选择在平面中的任意位置,甚至与这
n 个点的位置重合。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| #include <bits/stdc++.h> using namespace std; int const N = 100 + 5; vector<pair<double, double>> a; const double inf = 2e6; double ans = inf;
double getd(pair<double, double> x, pair<double, double> y) { double dx = x.first - y.first; double dy = x.second - y.second; return sqrt(dx * dx + dy * dy); }
double getsum(pair<double, double> x) { double re = 0; for (auto& p : a) { re += getd(x, p); } ans = min(ans, re); return re; }
double rand(double l, double r) { return (double)rand() / RAND_MAX * (r - l) + l; }
void sa() { pair<double, double> p(rand(0, 1e4), rand(0, 1e4)); for (double t = 1e4; t > 1e-4; t *= 0.99) { pair<double, double> np(rand(p.first - t, p.first + t), rand(p.second - t, p.second + t)); double dt = getsum(np) - getsum(p); if (exp(-dt / t) > rand(0, 1)) { p = np; } } } int main() { int n; cin >> n; srand((unsigned)time(NULL)); for (int i = 0; i < n; ++i) { double x, y; cin >> x >> y; a.push_back({x, y}); } for (int i = 0; i < 100; ++i) sa(); cout << round(ans) << "\n"; }
|
技巧
由于较为玄学,所以需要多跑几次模拟退火
更改随机种子
卡时间,例如小于0.8秒就一直跑模拟退火,充分利用测评时间
超时说明降温系数太大了,降温过程太慢,方法:可以把0.999改为0.99