[OI] 模拟退火
模拟退火是一种适合求样本点较大的多峰函数极值的方法.
模拟退火有几个参数:初始温度(\(T_{0}\)),终止温度(\(T_{e}\))和降温参数 \(d\),具体地,模拟退火是让每次的当前温度 \(T\) 变为 \(d\times T\),直到终止,因此 \(T_{e}\) 应为一个很接近 \(0\) 的正数,\(d\) 应该为一个很接近 \(1\) 的小于 \(1\) 的正数.
模拟退火在每次会根据温度选择一个新的决策点,温度越高,选择的决策点距离当前最优决策点越远. 具体地,为了实现这一步,我们可以直接把温度乘进随机数里:
double ex=ansx+(rand()*2-RAND_MAX)*t;
double ey=ansy+(rand()*2-RAND_MAX)*t;
其中 \(ansx,ansy\) 为当前最优决策.
在找出当前决策点以后,就计算它的贡献,然后进行下一个决策点的选择:
- 假如新的决策点更优,直接选择该决策点
- 否则,有 \(e^{\frac{-\Delta w}{T}}\) 的概率选择这个新的决策(二项式分布,这一步的目的是防止它卡在局部最优解里)
第二步可以这么实现:
else if(exp(-de/t)*RAND_MAX>rand()){
ansx=ex,ansy=ey;
}
为了让模拟退火正确的概率更高,可以考虑如下几点:
- 换 srand() 纯看脸
- 多跑几遍模拟退火(推荐用 clock() 卡时间跑完,充分利用每一毫秒)
- 多调调参
UPD:Linux 环境下 clock() 的单位是微秒
JSOI2004 平衡点
本题的权值函数为系统重力势能之和,能量越低越稳定,因此权值低的为更优解.
我选择调 srand,用 CTHOIissb
在 \(233\) 进制意义下的哈希值过了,难绷
#include<bits/stdc++.h>
using namespace std;
using namespace std;
int n;
struct node{
int x,y,w;
}a[2001];
double ansx,ansy,answ;
inline double energy(double x,double y){
double r=0,dx,dy;
for(int i=1;i<=n;++i){
dx=x-a[i].x;
dy=y-a[i].y;
r+=sqrt(dx*dx+dy*dy)*a[i].w;
}
return r;
}
void sa(){
double t=3000,down=0.996;
while(t>1e-15){
double ex=ansx+(rand()*2-RAND_MAX)*t;
double ey=ansy+(rand()*2-RAND_MAX)*t;
double ew=energy(ex,ey);
double de=ew-answ;
if(de<0){
ansx=ex;ansy=ey;answ=ew;
}
else if(exp(-de/t)*RAND_MAX>rand()){
ansx=ex,ansy=ey;
}
t*=down;
}
}
unsigned long long _hash(string x){
unsigned long long ans=0;
for(char i:x){
ans=ans*233+i;
}
return ans;
}
int main(){
int st=clock();
srand(_hash("CTHOIissb"));
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;
answ=energy(ansx,ansy);
while(clock()-st<=1000*980) sa();
printf("%.3lf %.3lf",ansx,ansy);
}