【知识】模拟退火

模拟退火

概念:

  1. 温度(步长):

    • 初始温度 \(T\)

    • 终止温度

    • 衰减系数 $ 0 \sim 1$

  2. 随机选择一个点:

    \(f(新点) - f(当前点) = \Delta E\)

    • \(\Delta E < 0\) 跳到新点
    • \(\Delta E>0\) 以一定概率跳过去,概率为 \(e^{- \frac{\Delta E}{T}}\)

    如何退火(降温)?

    模拟退火时我们有三个参数:初始温度 \(T_0\),降温系数 \(d\),终止温度 \(T_k\)。其中 \(T_0\) 是一个比较大的数,\(d\) 是一个非常接近 \(1\) 但是小于 \(1\) 的数,\(T_k\) 是一个接近 \(0\) 的正数。

    首先让温度 \(T=T_0\),然后按照上述步骤进行一次转移尝试,再让 \(T=d\cdot T\)。当 \(T<T_k\) 时模拟退火过程结束,当前最优解即为最终的最优解。

    注意为了使得解更为精确,我们通常不直接取当前解作为答案,而是在退火过程中维护遇到的所有解的最优值。

    过程如下图:

技巧:

卡时:while ((double)clock()/CLOCKS_PER_SEC < MAX_TIME) simulateAnneal();

这里的 MAX_TIME 是一个自定义的略小于时限的数(单位:s)。

题型:

A Star not a Tree?

模拟退火裸题,也可以用三分套三分做

cpp 复制代码
#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] 保龄球

对于每个轮次,有三种情况:全中,补中,失误。我们需要将打出的所有轮次的顺序重新排列,使得得分最高。

其中,补中会使选手在下一轮中的第一次尝试的得分将会以双倍计入总分。失误的情况属于一般情况,不具有特殊性,所以不做处理。最重要的是全中的情况。全中会使选手在计算总分时,下一轮的得分将会被乘 \(2\) 计入总分,最需要 特殊处理 的是,当原来最后一轮次全中,我们在重新排列的时候,也需要最后一轮次是全中,因为这样子才会有奖励的轮次,需要进行的轮数和重排前所进行的轮数是一致的,才满足题意。

本题目中,我们用温度 \(T\) ,表示答案更新范围,当 \(T \rightarrow 0\),也就是达到终止温度 \(T_E\) 时,我们就获得了一个答案。

如何随机一个序列,只需要随机交换两个数即可。

cpp 复制代码
#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] 均分数据

已知 \(n\) 个正整数 \(a_1,a_2 ... a_n\) 。将它们分成 \(m\) 组,使得方差最小。

\[\sigma = \sqrt{\frac 1m \sum\limits_{i=1}^m(\overline x - x_i)^2},\overline x = \frac 1m \sum\limits_{i=1}^m x_i \]

其中 \(\sigma\) 为均方差,\(\bar{x}\) 为各组数据和的平均值。

原式:

\[\sigma=\sqrt{\sum_{i=1}^n\ (x_i-\bar{x})^2 \over n}\quad\bar{x}={\sum_{i=1}^n\ x_i \over n} \]

化简:

\[n\sigma^2=\sum_{i=1}^n\ (x_i-\bar{x})^2 \]

拆开:

\[n\sigma^2=\sum_{i=1}^n x_i^2-2\bar{x}\sum_{i=1}^{n}x_i + \sum_{i=1}^{n}\bar{x}^2 \]

\(\because -2\bar{x}\sum_{i=1}^{n}x_i + \sum_{i=1}^{n}\bar{x}^2\) 为定值

也可以推测出当 \(\sum_{i=1}^{n}x_i\) 为定值,每个 \(x\) 尽量接近时,\(\sum_{i=1}^n x_i^2\) 最大。

于是我们可以将数组 random_shuffle 若干次,每次贪心的取值,使每个 x 尽量相等(把新加进来的数,加给最小 x),取最大值即可

cpp 复制代码
#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;
}
相关推荐
云卓SKYDROID2 天前
无人机航测VS传统测绘
无人机·科普·知识·航测·云卓科技
云卓SKYDROID4 天前
无人机节气门控制技术概述!
无人机·科普·知识·云卓科技·节气门控制
云卓SKYDROID20 天前
无人机地面遥控遥测技术与算法概述!
算法·无人机·科普·知识·云卓科技
云卓SKYDROID2 个月前
无人机声学侦测算法详解!
算法·无人机·科普·知识·云卓科技
云卓SKYDROID2 个月前
无人机遗传算法详解!
无人机·科普·遗传算法·知识·云卓科技
lee_LLL2 个月前
NK 数学定义
知识
云卓SKYDROID2 个月前
5G对无人机的影响!
5g·无人机·科普·知识·云卓科技
云卓SKYDROID2 个月前
碳钎维:无人机轻量化关键材料!
无人机·科普·知识·云卓科技·碳钎维
云卓SKYDROID3 个月前
无人机光电吊舱的技术!!
无人机·科普·知识·热成像·吊舱·云卓科技