模拟退火是一种随机化算法(仙术),比赛中的骗分神器
例题
在二维平面上有 n 个点,第 i 个点的坐标为 (xi,yi)。
请你找出一个点,使得该点到这 n 个点的距离之和最小。
该点可以选择在平面中的任意位置,甚至与这 n 个点的位置重合。
输入格式
第一行包含一个整数 n。
接下来 n 行,每行包含两个整数 xi,yi,表示其中一个点的位置坐标。
输出格式
输出最小距离和,答案四舍五入取整。
数据范围
1≤n≤100,
0≤xi,yi≤10000
输入样例:
4
0 0
0 10000
10000 10000
10000 0
输出样例:
28284
对于这道题,可以用模拟退火的算法(仙术)解决
步骤:
1:在整个N * N区域内随机选取一个点X
2:计算出 X 点距离题目中各点的距离F(X)
3:设 T=0.99(衰减系数)
4:在X为中心的(N * T) * (N * T)的区域内再选择一个点Y
5:计算出Y点距离题目中各点的距离F(Y)
6:如果F(Y)的值要小于F(X)的值,那么就令X等于Y
7:如果F(Y)的值要大于F(X)的值,那么就令X 几率性 的等于Y
(一般来说,这个几率常常用e^(-(F(Y)-F(X)/T)<rand(0,1)来实现)
8:回到步骤4,直到区域的范围小于设定的精度(比如1e-4),就停止算法
9:为了增加正确几率,还可以多进行几次模拟退火
伪代码:
void simulate_anneal(){
随机一个初始点
for(int T=范围上界;T>精度;T=T*衰减系数){
在当前点周围随机一个点
E=f(新点)-f(当前点);
if(满足更优的条件){
跳到新点
}else{
几率跳到新点
}
}
}
注意:模拟退火仅适用于题目中这种函数关系式是连续性的问题(说人话就是可导)
衰减系数和模拟次数都要根据题目给出的范围进行调整,保证不要超时
可以用以下代码来限制模拟次数
while ((double)clock() / CLOCKS_PER_SEC < 0.8) {
simulate_anneal();
}
完整代码
#include <bits/stdc++.h>
#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.99) {
PDD np(rand(cur.x - t, cur.x + t), rand(cur.y - t, cur.y + t)); //随机一个新点
auto dt = calc(np) - calc(cur); //新旧点距离差
if (exp(-dt / t) > rand(0, 1))
cur = np; //e^(-dt/t) 如果dt>0,exp必定大于1,如果dt<0,exp必定大于0小于1
}
}
int main() {
cin >> n;
for (int i = 0; i < n; i++)
cin >> q[i].x >> q[i].y;
for (int i = 0; i < 100; i++) { //模拟一百次
simulate_anneal();
}
printf("%.0lf\n",ans);
return 0;
}