爬山算法 & 模拟退火
爬山算法
简介
爬山算法就是求类似「单峰函数极值」问题的算法。
- 单峰函数极值?三分!
想法很美好,但是你觉得出题人会老老实实的让你用三分吗?
一个二维空间让你求一个极值还算好说,那三位,四维呢?反正我不会三分。
所以我们就只能回来学一下这个诡异的爬山算法。
为什么说它诡异?如果你看了一下标签就知道这是一个随机算法!后面的就自己悟吧。
过程
假设这里有一个「山」(单峰函数),你打算去山顶(函数最大值),但你不知道要往哪走,于是你开始乱扔末影珍珠(取随机值),要是落点比当前更优就传送上去。
你一开始比较急(因为再不快就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}\)
根据物理知识,能量函数可以定义为:
细节方面请看代码:
#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;
}


浙公网安备 33010602011771号