模拟退火

由于算法基于随机,不适宜作为题目正解,但在参数调得好的时候可以得到较高分数甚至AC,用于实在迫于无奈时的算法或暴力算法范围外的骗分算法

典型的邪教算法(我就喜欢邪教的)

基本思想是随机转移,从而确定最优解,大多数用于计算几何(但其实你如果够牛也可以在其他情况下用来骗分)

基本的实现步骤是:

1.取一个经验总结下的可能性较大的最优解(贪心或不正经结论)
2.按照当前选择的解进行转移,并设定较为科学的转移幅度
3.求出当前随机解的贡献,然后按照最优解必选、一定概率接受劣解的原则选取解
4.设置好初始温度,降温系数,结束温度等餐数(需要多次调试)同时为了保证解的最优性,可以进行多次退火。

对于第一点,可以通过编写暴力代码发现一些不正经结论,但是这个对正解的影响不大

个人认为最有难度的是第2,4点的调参,直接决定里你的退火找到正确答案的能力(参数大了超时,小了又会WA)

对于第3点我们的选取策略是:(条件对于求最小值,最大值则条件反过来即可)

\[P(\triangle E)=\left\{ \begin{aligned} {1} &&(\triangle E<0)\\ {e^{ \frac {-\triangle E} {T}}} && (\triangle E>=0)\\ \end{aligned} \right. \]

那么我给出退火参考代码:
P1337

#include<bits/stdc++.h>//由于是退火,所以不能保证稳过
using namespace std;
#define file(a) freopen(#a".in","r",stdin),freopen(#a".out","w",stdout)
#define N 1010
int n; 
int x[N],y[N],w[N];
double ansx,ansy,answ;
double energy(double tx,double ty)
{
	double res=0,sx,sy;
	for(int i=1;i<=n;++i)
	{
		sx=tx-x[i];
		sy=ty-y[i];
		res+=sqrt(sx*sx+sy*sy)*w[i];
	}
	return res;
}
void temperature_down()
{
	double tem=3000;
	while(tem>1e-15)
	{
		//printf("%lf %lf %lf\n",ansx,ansy,answ);
		double tx=ansx+(rand()*2-RAND_MAX)*tem;
		double ty=ansy+(rand()*2-RAND_MAX)*tem;
		double now_ans=energy(tx,ty);
		double delt=now_ans-answ;
		if(delt<0)
		{
			ansx=tx;ansy=ty;
			answ=now_ans;
		}
		else if(exp(-delt/tem)*RAND_MAX>rand())
		{
			ansx=tx;ansy=ty;
			//answ=now_ans;
		}
		tem*=0.996;
	}
}
void td(int times)
{
	while(times--)
	{
		temperature_down();
	}
}
int main()
{
	srand(time(NULL));
	scanf("%d",&n);
	for(int i=1;i<=n;++i)
	{
		scanf("%d%d%d",&x[i],&y[i],&w[i]);
		ansx+=x[i];
		ansy+=y[i];
	}
	ansx/=n;ansy/=n;
	answ=energy(ansx,ansy);
	td(5);
	printf("%.3lf %.3lf",ansx,ansy);
	return 0;
}

posted @ 2022-02-24 11:49  cbdsopa  阅读(42)  评论(0编辑  收藏  举报