爬山算法 & 模拟退火

爬山算法

简介

爬山算法就是求类似「单峰函数极值」问题的算法。

  • 单峰函数极值?三分!

想法很美好,但是你觉得出题人会老老实实的让你用三分吗?
一个二维空间让你求一个极值还算好说,那三位,四维呢?反正我不会三分。
所以我们就只能回来学一下这个诡异的爬山算法。
为什么说它诡异?如果你看了一下标签就知道这是一个随机算法!后面的就自己悟吧。

过程

假设这里有一个「山」(单峰函数),你打算去山顶(函数最大值),但你不知道要往哪走,于是你开始乱扔末影珍珠(取随机值),要是落点比当前更优就传送上去。
你一开始比较急(因为再不快就TLE了),于是你扔的范围比较大,后面越来越冷静你扔的范围越来越小,最后找到极值。

在具体实现上我们会定义三个变量,以及一个函数:

  • \(T\) (初始温度)
  • \(K\) (降温速度)
  • \(T_0\) (结束温度)
  • \(E(x)\) (能量函数)

就是先rand一个值,然后检查一下这个值是不是比当前答案更优,如果是,就将当前答案赋成这个rand值,否则就不管。

在每一次rand之后,都要让 \(T\) 降温,就是让 \(T\) 乘上 \(K\),这样可以使温度越降越慢,提升准确度。

另外,温度越低,rand的范围越小。

再取值上,既要保证答案足够精确,又要保证不会TLE。
再不会T的情况下,\(T\) 越大越好,\(K\) 通常取 \([0.985,0.995]\) 之间的实数,\(T_0\) 保证了精度,再精度要求较高的情况下可以取 \([1e-9,1e-15]\),当然就是在不会T的情况下越小越好(千万不能等于零)

模拟退火

简介

当然出题人肯定不会让你好受,搞出来个多峰函数求最值,就可以使用模拟退火:


摘自OI Wiki

可以看它会跳到比当前答案不优的地方,这也就是模拟退火可以求多峰函数极值的原因。

过程

模拟退火的实现过程与爬山基本一样,区别就是当当前rand值不比当前答案优时,依然有概率跳过去,但是为保证答案的准确性,答案依旧时记录最优项。
这样说可能不太明白,下面说几道例题。

例题

平衡点 / 吊打XXX\(^{luoguP1337}\)

根据物理知识,能量函数可以定义为:

\[E(x,y)=\sum_{i=1}^{n}w_i\sqrt{(x-x_i)^2+(y-y_i)^2} \]

细节方面请看代码:

#include<bits/stdc++.h>
using namespace std;
const int NUM=1e4+10;
const int inf=0x3f3f3f3f;

int n;
struct node{
	double x,y,w;
}A[NUM];
double K=0.999;
double ansx,ansy,ans;

double E(double x,double y){
	double sum=0;
	for(int i=1;i<=n;++i){
		sum+=sqrt((A[i].x-x)*(A[i].x-x)+(A[i].y-y)*(A[i].y-y))*A[i].w;
	}
	return sum;
}

void work(){
	double T=10000;
	while(T>1e-15){
		double x=ansx+(rand()*2-RAND_MAX)*T;//T越小范围越小
		double y=ansy+(rand()*2-RAND_MAX)*T;
		double tmp=E(x,y),d=tmp-ans;
		if(d<0){
			ansx=x,ansy=y,ans=tmp;
		}else if(exp(-d/T)*RAND_MAX>rand()){
			//不优时也有概率跳过去,但ans不更新
			//这个 exp(-d/T)*RAND_MAX>rand() 就是 T 越小概率越大 
			ansx=x,ansy=y;
		}
		T*=K;
	}
}

signed main(){
	srand(23751146);//玄学种子 
	cin>>n;
	for(int i=1;i<=n;++i){
		cin>>A[i].x>>A[i].y>>A[i].w;
		ansx+=A[i].x,ansy+=A[i].y;
	}
	ansx/=n,ansy/=n;ans=E(ansx,ansy);
	work();work();//多跑两遍 
	printf("%.3lf %.3lf\n",ansx,ansy);
	return 0;
}

posted @ 2026-08-15 15:53  LZYXT  阅读(8)  评论(0)    收藏  举报