[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\) 为当前最优决策.

在找出当前决策点以后,就计算它的贡献,然后进行下一个决策点的选择:

  1. 假如新的决策点更优,直接选择该决策点
  2. 否则,有 \(e^{\frac{-\Delta w}{T}}\) 的概率选择这个新的决策(二项式分布,这一步的目的是防止它卡在局部最优解里)

第二步可以这么实现:

else if(exp(-de/t)*RAND_MAX>rand()){
	ansx=ex,ansy=ey;
}

为了让模拟退火正确的概率更高,可以考虑如下几点:

  1. 换 srand() 纯看脸
  2. 多跑几遍模拟退火(推荐用 clock() 卡时间跑完,充分利用每一毫秒)
  3. 多调调参

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);
}
posted @ 2024-07-30 08:50  HaneDaniko  阅读(36)  评论(0编辑  收藏  举报