搜索 模拟退火

模拟退火是一种随机化算法(仙术),比赛中的骗分神器

例题
在二维平面上有 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;
}

Guess you like

Origin blog.csdn.net/fdxgcw/article/details/119668962