模拟退火学习笔记

前言

博主这个暴力骗分选手get到了
人生的本质

前置

有一类函数,我们要求其的最低点/最高点
二分?三分?四五六七八九分??
哦凉凉了…

先介绍一个爬山
我们随机撒点,然后让这些点去做类似现实的爬山
即右边优就去右边,左边优就去左边
直到没有比他更优秀的了!
也就是相当于到山峰了吧

那么这个贪心的算法显然非常容易卡在一个局部最优解出不来了
哦怎么办呢…
按正常人的思维想,走到了一个最高处,就应该换个地方玩耍
玩耍也是要有思维的,怎么说也得要有点判断性嘛
怎么给程序加判断性呢
模拟退火就此出现

虽然听说退火日常打不过爬山??

玩法

扯皮一下物理知识
一个物体要降温,从内能高降低到内能低,并不是越快越好
我们需要一个徐徐降温的过程,来让其内部的微观结构结晶来最小化内能
当然以上无关

模拟退火就是基于以上操作
我们先给其一个温度 T T T,再给他一个降温系数 D D D,表示每次让温度变为 T ∗ D T*D TD
自然这个 D D D不能太小了

当一个物体内能大的时候,他有充分的时间与几率去做一些冒险的事情,调整其最优位置
反正他的分子能大,跑远了后面还是有回来的空间
反之分子能小的时候,他就不能变得活跃了
我们的模拟退火同样也要基于以上的思路

定义几个变量
x x x表示当前位置
Δ x \Delta x Δx表示 x x x的增加量,其取值范围应该与 T T T成正比关系,因为分子能越大他能跑的地方是越远的
T T T表示当前温度
D D D表示温度变动系数,一般取在 [ 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)

给一个初始解 x x x,徐徐降温并让其在合适范围内移动
取到下一个位置 x 1 = x + Δ x x1=x+\Delta x x1=x+Δx
计算当前位置 x 1 x1 x1的解 a 1 a1 a1
比较 a 1 a1 a1 x x x位置的解 a 2 a2 a2
如果 a 1 a1 a1 a 2 a2 a2优,此时显然可以进行移动
否则,我们要以一定的概率获得是否移动的信号
科学家不懈钻研过后获得了这个概率为 e Δ F T e^{\frac{\Delta F}{T}} eTΔF
然后降温
反复执行上述过程直到 T ≤ e p s T\leq eps Teps,此时视为物体内能为 0 0 0
在实现的过程中,为了避免最后可能移到没有途中的解优秀的位置
我们可以记录一个中途的解的最大值来弥补

由于随机算法的不稳定性
我们可以多跑几次来确定解

例题

正常人都会选这个

bzoj3680: 吊打XXX

自然界一切物体都在向着能量大->能量小的位置移动
故我们要让这个模型整体能量尽量小
则让这个模型的重力势能尽量小
则让其在桌面上的绳子尽量短
那么就是让 ∑ d i ∗ l e n i \sum d_i*len_i dileni尽量大,其中 l e n i len_i leni表示长度
过程中有一个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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值