笔记——模拟退火
蓝月の笔记——模拟退火篇
摘要
随机化乱搞,记得洗脸
Part 1 原理
随机答案,在其中寻找最优解
Part 2 实现
首先对于买一个状态定义一个能量函数 \(E(x)\),为假设最优点为 \(x\) 时的答案。定义当前温度 \(tpr\),初始温度 \(T_s\),降温系数 \(\Delta T\),结束温度 \(T_e\)
我们随机一个答案作为备选答案,随机的不确定性由当前温度决定。若当前备选答案由于当前最优解,则替换最优解。否则以一定概率接受这个解,具体地,这个概率为 \(e^{\frac{-\Delta E}{tpr}}\),其中 \(\Delta E\) 为当前最优解与备选答案的差
以 \(tpr=T_s\) 开始运行,在 \(tpr < T_e\) 时结束,每次 \(tpr \gets tpr \times \Delta T\) 来降温
为了使答案更准确,可以使用卡时技巧,时间没到就一直跑
Part 3 例题
Luogu P1337 [JSOI2004] 平衡点 / 吊打XXX
考虑能量函数,显然对于一个点来说,每一根线对它造成的势能总和越结晶 \(0\),它就越有可能时答案。那么定义 \(E([x,y])=\displaystyle\sum_{i=1}^{n}dis([x,y],[a_{i_x},a_{i_y}]) \times a_{i_w}\)
然后按模拟退火过程处理即可
代码:
// BLuemoon_
#include <bits/stdc++.h>
using namespace std;
using DB = double;
const int kMaxN = 1e3 + 5;
const DB kTpr = 1e4, kDlt = 0.99;
struct P {
DB x, y, we;
};
int n;
P e[kMaxN];
DB X, Y, ans = 1e18, tpr;
DB E(DB l, DB r, DB s = 0, DB dx = 0, DB dy = 0) {
for (int i = 1; i <= n; i++) {
dx = l - e[i].x, dy = r - e[i].y;
s += (sqrt(dx * dx + dy * dy) * e[i].we);
}
return s;
}
DB D() {
return (rand() * 2 - RAND_MAX) * tpr;
}
void SA(DB nx = 0, DB ny = 0) {
for (tpr = kTpr, nx = X, ny = Y; tpr > 1e-13;) {
DB xt = X + D(), yt = Y + D(), E_ = E(xt, yt), dlt = E_ - ans;
if (dlt < 0) {
X = nx = xt, Y = ny = yt, ans = E_;
} else {
if (exp(-dlt / tpr) * RAND_MAX > rand()) {
nx = xt, ny = yt;
}
}
tpr *= kDlt;
}
}
int main() {
srand(time(0));
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> e[i].x >> e[i].y >> e[i].we;
}
for (; clock() * 1.0 / CLOCKS_PER_SEC <= 0.95; SA()) {
}
cout << fixed << setprecision(3) << X << ' ' << Y << '\n';
return 0;
}
注意参数调整和洗脸
特别注意洗脸
习题
Luogu P2210 Haywire 利用随机化来扰动排列
Luogu P3878 [TJOI2010] 分金币 同样时扰动排列,注意 \(E\) 函数计算方式