模拟退火
由于算法基于随机,不适宜作为题目正解,但在参数调得好的时候可以得到较高分数甚至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;
}