文章目录
- 前言
- 解析
- 流程
- 示例代码
- trick
所谓模拟退火,就是通过代码模拟退火
(逃)
前言
终于学了这个神奇的骗分算法
几次在大赛中都发现这算法是真的有学的必要
FFC可能真的要想想自己的题目对OI界的导向作用了
但学完以后还是感觉挺有意思的,无脑艹题也很香
似乎绝大多数教程都会在这个地方贴一个百度百科在物理学上对退火的定义
但我并不想贴
因为个人感觉这个算法虽然说是模拟但感觉完全不需要知道任何关于退火的知识…
模拟退火,其实可以理解成模拟降温
解析
流程
其实模拟退火代码框架很简单
总体如下:
void SA(){//设置初温//生成一个较优的初始解,并计算初始答案 while(T>1e-10){//生成次优解,计算答案优劣变化(能量变化量)if(/*接受新的解*/){//更新当前解 }T*=t;//降温}
}
就这么短
其中,计算答案优劣变化和初始答案通常会外包一个 calc 函数,也是许多时候决定一个退火算法优劣的关键
至于是否接受当前解,分情况来讨论:
设答案变化量为 xxx,当前求答案最小值 (降温)
- 如果答案变优(x<0x<0x<0),直接更新当前解
- 否则(x≥0x\ge 0x≥0),接受新解的概率为:e−xTe^{\frac{-x}{T}}eT−x
定性来看,这个东西是和谐的,答案变差的越多,我们接受的概率就越小
示例代码
P1337 [JSOI2004]平衡点 / 吊打XXX
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define debug(...) fprintf(stderr,__VA_ARGS__)
const int N=1050;
const double eps=1e-12;
inline ll read(){ll x(0),f(1);char c=getchar();while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}while(isdigit(c)) {x=(x<<1)+(x<<3)+c-'0';c=getchar();}return x*f;
}int n;
double x[N],y[N],w[N];
double T,t,X,Y;
void SA(){T=1e5,t=0.95;for(int i=1;i<=n;i++) X+=x[i],Y+=y[i];X/=n;Y/=n;while(T>1e-5){double fx(0),fy(0);for(int i=1;i<=n;i++){double dx=x[i]-X,dy=y[i]-Y;if(abs(dx)<1e-15&&abs(dy)<1e-15) continue;double per=1.0/sqrt(dx*dx+dy*dy);fx+=w[i]*dx*per;fy+=w[i]*dy*per;}X+=fx*T;Y+=fy*T;T*=t;}printf("%.3lf %.3lf\n",X,Y);
}
signed main() {
#ifndef ONLINE_JUDGE//freopen("a.in","r",stdin);//freopen("a.out","w",stdout);
#endifn=read();for(int i=1;i<=n;i++){x[i]=read();y[i]=read();w[i]=read();}SA();return 0;
}
/**/
trick
- 卡时:模拟退火的时间限制总是多多益善,可以说,任何时候你都没有理由不给退火卡时(模拟退火可以WA,但绝对不能T,注意算好时间!!!)
- calc函数别真就硬暴力,可以配合一些贪心
- 调参:离不开的梦魇,我也说不清楚,但从这道题来看,降温调的慢一些似乎是个不错的选择
- 可以在同一温度使用多次迭代尝试更新多次(但是看多篇题解这种写法似乎并不主流)
Thanks for reading!