前言
博主这个暴力骗分选手get到了
人生的本质
前置
有一类函数,我们要求其的最低点/最高点
二分?三分?四五六七八九分??
哦凉凉了…
先介绍一个爬山
我们随机撒点,然后让这些点去做类似现实的爬山
即右边优就去右边,左边优就去左边
直到没有比他更优秀的了!
也就是相当于到山峰了吧
那么这个贪心的算法显然非常容易卡在一个局部最优解出不来了
哦怎么办呢…
按正常人的思维想,走到了一个最高处,就应该换个地方玩耍
玩耍也是要有思维的,怎么说也得要有点判断性嘛
怎么给程序加判断性呢
模拟退火就此出现
虽然听说退火日常打不过爬山??
玩法
扯皮一下物理知识
一个物体要降温,从内能高降低到内能低,并不是越快越好
我们需要一个徐徐降温的过程,来让其内部的微观结构结晶来最小化内能
当然以上无关
模拟退火就是基于以上操作
我们先给其一个温度TTT,再给他一个降温系数DDD,表示每次让温度变为T∗DT*DT∗D
自然这个DDD不能太小了
当一个物体内能大的时候,他有充分的时间与几率去做一些冒险的事情,调整其最优位置
反正他的分子能大,跑远了后面还是有回来的空间
反之分子能小的时候,他就不能变得活跃了
我们的模拟退火同样也要基于以上的思路
定义几个变量
xxx表示当前位置
Δx\Delta xΔx表示xxx的增加量,其取值范围应该与TTT成正比关系,因为分子能越大他能跑的地方是越远的
TTT表示当前温度
DDD表示温度变动系数,一般取在[0.95,0.99][0.95,0.99][0.95,0.99]的区间内
ΔF\Delta FΔF表示解的变动值,等于f(x)−f(Δx)f(x)-f(\Delta x)f(x)−f(Δx)
给一个初始解xxx,徐徐降温并让其在合适范围内移动
取到下一个位置x1=x+Δxx1=x+\Delta xx1=x+Δx
计算当前位置x1x1x1的解a1a1a1
比较a1a1a1与xxx位置的解a2a2a2
如果a1a1a1比a2a2a2优,此时显然可以进行移动
否则,我们要以一定的概率获得是否移动的信号
科学家不懈钻研过后获得了这个概率为eΔFTe^{\frac{\Delta F}{T}}eTΔF
然后降温
反复执行上述过程直到T≤epsT\leq epsT≤eps,此时视为物体内能为000
在实现的过程中,为了避免最后可能移到没有途中的解优秀的位置
我们可以记录一个中途的解的最大值来弥补
由于随机算法的不稳定性
我们可以多跑几次来确定解
例题
正常人都会选这个
bzoj3680: 吊打XXX
自然界一切物体都在向着能量大->能量小的位置移动
故我们要让这个模型整体能量尽量小
则让这个模型的重力势能尽量小
则让其在桌面上的绳子尽量短
那么就是让∑di∗leni\sum d_i*len_i∑di∗leni尽量大,其中lenilen_ileni表示长度
过程中有一个RAND_MAX的函数,可以取到Rand的最大值,用于生成一个[0,1][0,1][0,1]之间的随机数来获取是否用目标解替换当前解
代码很好懂
本质在于调参
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#include<ctime>
#include<map>
#include<bitset>
#include<set>
#define LL long long
#define mp(x,y) make_pair(x,y)
#define pll pair<long long,long long>
#define pii pair<int,int>
#define double long double
using namespace std;
inline int read()
{
int f=1,x=0;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
int stack[20];
inline void write(int x)
{
if(x<0){putchar('-');x=-x;}
if(!x){putchar('0');return;}
int top=0;
while(x)stack[++top]=x%10,x/=10;
while(top)putchar(stack[top--]+'0');
}
inline void pr1(int x){write(x);putchar(' ');}
inline void pr2(int x){write(x);putchar('\n');}
const int MAXN=10005;
const double D=0.97;
const double eps=1e-12;
int n;
double ux[MAXN],uy[MAXN],di[MAXN];
inline double sqr(double u){return u*u;}
double calc(double u1,double u2)
{
double ret=0;
for(int i=1;i<=n;i++)
ret+=sqrt((sqr(u1-ux[i])+sqr(u2-uy[i])))*di[i];
return ret;
}
int main()
{
srand(20021127);
n=read();
double bx=0,by=0,best,ans;
for(int i=1;i<=n;i++)
{
ux[i]=read(),uy[i]=read(),di[i]=read();
bx+=ux[i];by+=uy[i];
}
bx/=n;by/=n;
best=ans=calc(bx,by);
int times=1;//退几次火
while(times--)
{
double nx=bx,ny=by;
for(double T=100000;T>eps;T*=D)
{
double ax=T*(rand()*2-RAND_MAX),ay=T*(rand()*2-RAND_MAX);
ax+=nx;ay+=ny;
double nxt=calc(ax,ay);
if(best>nxt)best=nxt,bx=ax,by=ay;
if(ans>nxt||exp((ans-nxt)/T)>((double)rand()/RAND_MAX))ans=nxt,nx=ax,ny=ay;
}
}
printf("%.3Lf %.3Lf\n",bx,by);
return 0;
}