【BZOJ3680】吊打XXX-模拟退火
测试地址:吊打XXX
做法:本题需要用到模拟退火。
假设所有绳子的长度都相同,而且绳长都等于天花板到地面的高度,那么一个物品和地面的距离就等于绳结到对应的孔的距离。又因为重力势能和物品距地面的高度和物品本身的质量有关,而一个系统的总重力势能最小时,就是这个系统的平衡状态,于是我们转化成了求一对,使得:
最小。我们发现我们找不到任何方法在多项式时间内求出这个最小值,这个时候就要用模拟退火来解决了。
模拟退火是一种启发式搜索,它的算法过程是仿照现实中的退火效应设计的。算法过程如下:
首先,设定一个初始温度和一组初始解。
接下来,重复这一步直到温度下降到一个阈值内:随机移动解的位置,移动的距离随着的减小而减小,如果新的位置上的函数值比原先更优,则直接将解转移到这个位置,否则计算出一个转移的概率,有的概率转移到这个位置,最后温度下降。
最后,在已得到的最优解的周围进行小范围的随机走动,如果比当前解更优则转移,逐渐逼近最优解。
中间这个按照现实中的退火效应,一般设置为,即,分别表示当前的函数值和随机移动后位置的函数值。注意到当时,,即表示一定转移,否则,符合概率的定义。当然这个式子是在求最小值的时候用的,求最大值时在括号里加个负号即可。
至于其他参数的设置,不同的题目的最好参数各不相同,需要根据实际情况调参,才能既满足时间要求,也能得到一个比较优的解。这个算法一般在提交答案试题中常用。
以下是本人代码:
#include <bits/stdc++.h>
using namespace std;
int n;
double x[10010],y[10010],w[10010];
double ansx=0.0,ansy=0.0,D,ans;
double Rand()
{
return (double)(rand()%10000)/10000.0;
}
double calc(double nowx,double nowy)
{
double sum=0.0;
for(int i=1;i<=n;i++)
sum+=sqrt((nowx-x[i])*(nowx-x[i])+(nowy-y[i])*(nowy-y[i]))*w[i];
if (sum<D) ansx=nowx,ansy=nowy,D=sum;
return sum;
}
void work()
{
double dE,T=100000.0,nowx=ansx,E=D,nowy=ansy,newx,newy;
while(T>0.001)
{
newx=nowx+T*(2.0*Rand()-1.0);
newy=nowy+T*(2.0*Rand()-1.0);
dE=E-calc(newx,newy);
if (exp(dE/T)>Rand())
nowx=newx,nowy=newy,E-=dE;
T*=0.97;
}
for(int i=1;i<=1000;i++)
{
nowx=ansx+T*(2.0*Rand()-1.0);
nowy=ansy+T*(2.0*Rand()-1.0);
calc(nowx,nowy);
}
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%lf%lf%lf",&x[i],&y[i],&w[i]);
ansx+=x[i],ansy+=y[i];
}
ansx/=(double)n,ansy/=(double)n,D=calc(ansx,ansy);
work();
printf("%.3lf %.3lf",ansx,ansy);
return 0;
}