【知识】模拟退火 & 爬山法
模拟退火
概念:
-
温度(步长):
-
初始温度
-
终止温度
-
衰减系数
-
-
随机选择一个点:
跳到新点 以一定概率跳过去,概率为
如何退火(降温)?
模拟退火时我们有三个参数:初始温度
,降温系数 ,终止温度 。其中 是一个比较大的数, 是一个非常接近 但是小于 的数, 是一个接近 的正数。首先让温度
,然后按照上述步骤进行一次转移尝试,再让 。当 时模拟退火过程结束,当前最优解即为最终的最优解。注意为了使得解更为精确,我们通常不直接取当前解作为答案,而是在退火过程中维护遇到的所有解的最优值。
过程如下图:
技巧:
卡时:while ((double)clock()/CLOCKS_PER_SEC < MAX_TIME) simulateAnneal();
这里的 MAX_TIME
是一个自定义的略小于时限的数(单位:s)。
题型:
-
A Star not a Tree?
模拟退火裸题,也可以用三分套三分做
#include <iostream> #include <cstring> #include <algorithm> #include <cmath> #include <ctime> #define x first #define y second using namespace std; typedef pair<double, double> PDD; const int N = 110; int n; PDD q[N]; double ans = 1e8; double rand(double l, double r) { return (double)rand() / RAND_MAX * (r - l) + l; } double get_dist(PDD a, PDD b) { double dx = a.x - b.x; double dy = a.y - b.y; return sqrt(dx * dx + dy * dy); } double calc(PDD p) { double res = 0; for (int i = 0; i < n; i ++ ) res += get_dist(p, q[i]); ans = min(ans, res); return res; } void simulate_anneal() { PDD cur(rand(0, 10000), rand(0, 10000)); for (double t = 1e4; t > 1e-4; t *= 0.9) { PDD np(rand(cur.x - t, cur.x + t), rand(cur.y - t, cur.y + t)); double dt = calc(np) - calc(cur); if (exp(-dt / t) > rand(0, 1)) cur = np; } } int main() { scanf("%d", &n); for (int i = 0; i < n; i ++ ) scanf("%lf%lf", &q[i].x, &q[i].y); for (int i = 0; i < 100; i ++ ) simulate_anneal(); printf("%.0lf\n", ans); return 0; }
-
P4044 [AHOI2014/JSOI2014] 保龄球
对于每个轮次,有三种情况:全中,补中,失误。我们需要将打出的所有轮次的顺序重新排列,使得得分最高。
其中,补中会使选手在下一轮中的第一次尝试的得分将会以双倍计入总分。失误的情况属于一般情况,不具有特殊性,所以不做处理。最重要的是全中的情况。全中会使选手在计算总分时,下一轮的得分将会被乘
计入总分,最需要 特殊处理 的是,当原来最后一轮次全中,我们在重新排列的时候,也需要最后一轮次是全中,因为这样子才会有奖励的轮次,需要进行的轮数和重排前所进行的轮数是一致的,才满足题意。本题目中,我们用温度
,表示答案更新范围,当 ,也就是达到终止温度 时,我们就获得了一个答案。如何随机一个序列,只需要随机交换两个数即可。
#include <iostream> #include <cstring> #include <algorithm> #include <cmath> #include <ctime> #define x first #define y second using namespace std; typedef pair<int, int> PII; const int N = 55; int n, m; PII q[N]; int ans; int calc() { int res = 0; for (int i = 0; i < m; i ++ ) { res += q[i].x + q[i].y; if (i < n) { if (q[i].x == 10) res += q[i + 1].x + q[i + 1].y; else if (q[i].x + q[i].y == 10) res += q[i + 1].x; } } ans = max(ans, res); return res; } void simulate_anneal() { for (double t = 1e4; t > 1e-4; t *= 0.99) { int a = rand() % m, b = rand() % m; int x = calc(); swap(q[a], q[b]); if (n + (q[n - 1].x == 10) == m) { int y = calc(); int delta = y - x; if (exp(delta / t) < (double)rand() / RAND_MAX) swap(q[a], q[b]); } else swap(q[a], q[b]); } } int main() { cin >> n; for (int i = 0; i < n; i ++ ) cin >> q[i].x >> q[i].y; if (q[n - 1].x == 10) m = n + 1, cin >> q[n].x >> q[n].y; else m = n; for (int i = 0; i < 100; i ++ ) simulate_anneal(); cout << ans << endl; return 0; }
-
P2503 [HAOI2006] 均分数据
已知
个正整数 。将它们分成 组,使得方差最小。其中
为均方差, 为各组数据和的平均值。原式:
化简:
拆开:
为定值也可以推测出当
为定值,每个 尽量接近时, 最大。于是我们可以将数组
random_shuffle
若干次,每次贪心的取值,使每个 x 尽量相等(把新加进来的数,加给最小 x),取最大值即可。#include <iostream> #include <cstring> #include <algorithm> #include <cmath> using namespace std; const int N = 25, M = 10; int n, m; int w[N], s[M]; double ans = 1e8; double calc() { memset(s, 0, sizeof s); for (int i = 0; i < n; i ++ ) { int k = 0; for (int j = 0; j < m; j ++ ) if (s[j] < s[k]) k = j; s[k] += w[i]; } double avg = 0; for (int i = 0; i < m; i ++ ) avg += (double)s[i] / m; double res = 0; for (int i = 0; i < m; i ++ ) res += (s[i] - avg) * (s[i] - avg); res = sqrt(res / m); ans = min(ans, res); return res; } void simulate_anneal() { random_shuffle(w, w + n); for (double t = 1e6; t > 1e-6; t *= 0.95) { int a = rand() % n, b = rand() % n; double x = calc(); swap(w[a], w[b]); double y = calc(); double delta = y - x; if (exp(-delta / t) < (double)rand() / RAND_MAX) swap(w[a], w[b]); } } int main() { cin >> n >> m; for (int i = 0; i < n; i ++ ) cin >> w[i]; for (int i = 0; i < 100; i ++ ) simulate_anneal(); printf("%.2lf\n", ans); return 0; }
爬山法:
爬山算法每次在当前找到的最优方案
只能解决单峰函数问题,如果解决单峰问题可能会陷入局部最优解。
-
P4035 [JSOI2008] 球形空间产生器
题目大意: 给你
个点坐标,要你求出圆心题解: 随机化,可以随机一个点当圆心,然后和每个点比较,求出平均距离
,如果到这个点的距离大于 ,说明离这个点远了,就给圆心施加一个向这个点的力;若小于 ,说明近了,就施加一个远离这个点的力。所有点比较完后,把假设的圆心按合力方向移动一个距离,距离和当前温度有关。时间越久,温度越低#include <iostream> #include <cstring> #include <algorithm> #include <cmath> using namespace std; const int N = 15; int n; double d[N][N]; double ans[N], dist[N], delta[N]; void calc() { double avg = 0; for (int i = 0; i < n + 1; i ++ ) { dist[i] = delta[i] = 0; for (int j = 0; j < n; j ++ ) dist[i] += (d[i][j] - ans[j]) * (d[i][j] - ans[j]); dist[i] = sqrt(dist[i]); avg += dist[i] / (n + 1); } for (int i = 0; i < n + 1; i ++ ) for (int j = 0; j < n; j ++ ) delta[j] += (dist[i] - avg) * (d[i][j] - ans[j]) / avg; } int main() { scanf("%d", &n); for (int i = 0; i < n + 1; i ++ ) for (int j = 0; j < n; j ++ ) { scanf("%lf", &d[i][j]); ans[j] += d[i][j] / (n + 1); } for (double t = 1e4; t > 1e-6; t *= 0.99995) { calc(); for (int i = 0; i < n; i ++ ) ans[i] += delta[i] * t; } for (int i = 0; i < n; i ++ ) printf("%.3lf ", abs(ans[i])); return 0; }
很容易想到的是,为了尽可能获取优秀的答案,我们可以多次爬山。方法有修改初始状态/修改降温参数/修改初始温度等,然后开一个全局最优解记录答案。每次爬山结束之后,更新全局最优解。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!