【BZOJ3680】吊打XXX-模拟退火

测试地址:吊打XXX
做法:本题需要用到模拟退火。
假设所有绳子的长度都相同,而且绳长都等于天花板到地面的高度,那么一个物品和地面的距离就等于绳结到对应的孔的距离。又因为重力势能和物品距地面的高度和物品本身的质量有关,而一个系统的总重力势能最小时,就是这个系统的平衡状态,于是我们转化成了求一对(x,y),使得:
i=1n(xxi)2+(yyi)2wi
最小。我们发现我们找不到任何方法在多项式时间内求出这个最小值,这个时候就要用模拟退火来解决了。
模拟退火是一种启发式搜索,它的算法过程是仿照现实中的退火效应设计的。算法过程如下:
首先,设定一个初始温度T和一组初始解。
接下来,重复这一步直到温度下降到一个阈值内:随机移动解的位置,移动的距离随着T的减小而减小,如果新的位置上的函数值比原先更优,则直接将解转移到这个位置,否则计算出一个转移的概率p,有p的概率转移到这个位置,最后温度T下降。
最后,在已得到的最优解的周围进行小范围的随机走动,如果比当前解更优则转移,逐渐逼近最优解。
中间这个p按照现实中的退火效应,一般设置为exp(EnowEnextT)exp(x)exEnow,Enext分别表示当前的函数值和随机移动后位置的函数值。注意到当Enow>Enext时,p>1,即表示一定转移,否则0<p<1,符合概率的定义。当然这个式子是在求最小值的时候用的,求最大值时在括号里加个负号即可。
至于其他参数的设置,不同的题目的最好参数各不相同,需要根据实际情况调参,才能既满足时间要求,也能得到一个比较优的解。这个算法一般在提交答案试题中常用。
以下是本人代码:

#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;
}
posted @ 2018-05-20 16:58  Maxwei_wzj  阅读(101)  评论(0编辑  收藏  举报