模拟退火算法初探——[usaco 5.2]electic fences总结

今天心血来潮自己学习模拟退火,听名字就感觉高端大气上档次,老师说:这是一个拼人品的算法,他也不熟。建议我先不学,毕竟高大上,我喜欢。
下面谈谈今天的学习:
看了一些材料什么的,表示无爱,什么温度啊降温啊,真是看不懂,不过还是找到了一个好的文章,非常不错,讲的很通俗易懂,大家也可以去膜拜围观: 大白话解析模拟退火算法 orz!!!
模拟退火是爬山算法的改进,爬山算法是一种不成熟的贪心,它更多是倾向于局部最优,一旦找到的下一个策略不如现在,便不再搜索,所以很容易与实际最优解擦肩而过。
而模拟退火则是做了个改进,如果下一个策略比当前要优,与爬山算法相同,如果没有当前优,那么就有一定的几率继续搜索,并且这个几率越来越小,所以也就避免了盲目的停止搜索,也可以比较科学的继续搜索。不过说到底,它还是随机化搜索的一种,所以还是得拼人品的,像本人遇到的情况就比较坑。
好了,说一下模拟退火算法的基本步骤,这里我们的模型选取还是按照温度来讲:
首先,需要让系统温度总是大于你的下限,这是最基本的,至于下限是什么,有时候题目中会明确提及,而有时候需要你自己看情况,老师说,这基本上看经验了QAQ。。。
其次,要有一定的检验循环次数,因为这是随机化,难免出岔子,从理论上讲,循环的次数越多,就越有可能找到最优解,越保险,下面要说的这道题我循环了500次,结果WA了两组,改成1000次,AC了,真是拼人品啊!!!
在前面提到的两个循环中,就是核心内容了,首先,如果新的策略更优,就更新,如果不优,就按照下面的来: if(exp(dE/T)>random(0,1)) Y(i+1)=Y(i); 一定概率的更新。
然后就开始降温退火了,要注意,你降温的幅度不能大也不能小,幅度大了程序运行会比较快,但是容易将最优解错过,幅度小了找到最优解的可能更大,但是时间上吃不消。具体开多大,还是老师那句话:看经验!!!

模拟退火的过程基本上就是这么多,不得不说这是我学程序设计以来学的最惊艳的算法了,之前的算法像什么图论动规不管多屌,还是那么些数字,必须按照人家的数字走,可是这。。看经验,确实很厉害!!!
我学的也并不深,以后会继续学习,这篇总结写的可能很次,也会有些错误,请及时指出,谢谢!

下面看一道模拟退火的题目:


这道题方法很多,据NOCOW所说,其实数学计算甚至爬山算法都能AC,我只说模拟退火算法。
代码如下:
#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iomanip>
#include<ctime>
#include<cstdlib>
#define sqr(q) ((q)*(q))
#define ou(x,y,x1,y1) (sqrt(sqr((x)-(x1))+sqr((y)-(y1))))
using namespace std;
typedef double dd;
int N;
dd x,y,T,best;
struct dong
{
	int x1,x2,y1,y2;
}d[160];
dd dis(dd x,dd y,int k)
{
	if(d[k].x1==d[k].x2)//电网与y轴平行。
	{
		if(y<d[k].y1) return ou(x,y,d[k].x1,d[k].y1);//源点在1点下方则最短距离为1点与源点距离。
		if(y>d[k].y2) return ou(x,y,d[k].x2,d[k].y2);//源点在2点上方则最短距离为2点与源点距离。
		return abs(x-d[k].x1);//求点到线的距离。
	}
	else
	{
		if(x<d[k].x1) return ou(x,y,d[k].x1,d[k].y1);
		if(x>d[k].x2) return ou(x,y,d[k].x2,d[k].y2);
		return abs(y-d[k].y1);
	}
}
void init()
{
	scanf("%d",&N);
	x=rand()%100,y=rand()%100;
	T=100,best=0;
	for(int i=1;i<=N;i++)
	{
		scanf("%d%d%d%d",&d[i].x1,&d[i].y1,&d[i].x2,&d[i].y2);
		if(d[i].x1>d[i].x2) swap(d[i].x1,d[i].x2);
		if(d[i].y1>d[i].y2) swap(d[i].y1,d[i].y2);
		best+=dis(x,y,i);
	}
}
void SAA()
{
	int num=31;
	dd tx,ty;
	while(T>10e-3)//系统温度始终要高于一个下限T_min=10e-3。
	{
		for(int i=1;i<=1000;i++)//多次计算,理论上讲循环次数越多,越有可能得到最优解,一般按情况定。
		{
			dd x1,y1;
			x1=T*(dd(rand())/dd(RAND_MAX))*(2*(rand()%2)-1);//随机生成一组新的答案。
			y1=sqrt(sqr(T)-sqr(x1))*(2*(rand()%2)-1)+y;//需要注意当前解的范围应当适当。
			x1+=x;
			dd temp=0;
			for(int k=1;k<=N;k++) temp+=dis(x1,y1,k);
			dd p=temp-best,yy=0;//p代表dE,是能量差,就是模板里的J(i+1)-J(i)。
			if(p<0)//比当前最优解更优,因为这个是越小越好,更新最优解。
			{
				yy=1;
				tx=x1;ty=y1;
				best=temp;
			}
			else yy=exp(-p/T);//否则,计算概率,模拟退火。
			dd q=dd(rand())/dd(RAND_MAX);
			if(q<yy)//相当于模板中的if(exp(dE/T)>random(0,1))。
			{
				x=x1;
				y=y1;//更新解。
			}
		}
		num++;
		T=T/(log10(1.0*num)/log10(20.0));//降温退火,降温的大小越大,越有可能找到最优解,但时间较长;降温的大小越小,时间越短,但最终可能只搜到了局部最优。
	}
	printf("%.1lf %.1lf %.1lf",tx,ty,best);
}
int main()
{
	srand(size_t(time(NULL)));
	init();
	SAA();
	return 0;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值