【洛谷1337】[JSOI2004]吊打XXX(模拟退火经典题)

点此看题面

大致题意:一个平面上有 n 个点,每个点有1个权值,现在要选择平面上的一个点,使这 n 个点的权值乘上到达选定点的距离之和最小。

我们可以用模拟退火来做这道题。

先将 ( 0 , 0 ) 设定为答案,随后不断选取一个新的坐标,比较选择该点时的代价与当前答案的代价。若小于当前答案的代价,则更新答案,否则,将有一定概率更新答案(更新坐标的幅度时间的增大减小更新答案的概率时间的增大以及两个代价之差的增大减小)。

只要多模拟退火几遍,或者保持一颗虔诚的心,就能过了。

代码如下:

#include<bits/stdc++.h>
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
#define LL long long
#define swap(x,y) (x^=y,y^=x,x^=y)
#define delta 0.99
#define N 1000
using namespace std;
int n,x[N+5],y[N+5],w[N+5];
inline char tc()
{
    static char ff[100000],*A=ff,*B=ff;
    return A==B&&(B=(A=ff)+fread(ff,1,100000,stdin),A==B)?EOF:*A++;
}
inline void read(int &x)
{
    x=0;int f=1;char ch;
    while(!isdigit(ch=tc())) f=ch^'-'?1:-1;
    while(x=(x<<3)+(x<<1)+ch-'0',isdigit(ch=tc()));
    x*=f;
}
inline void write(int x)
{
    if(x<0) putchar('-'),x=-x;
    if(x>9) write(x/10);
    putchar(x%10+'0');
}
inline double dis(double nx,double ny)//计算出n个点的权值乘上到选定点的距离之和
{
    register int i;double res=0.0;
    for(i=1;i<=n;++i) res+=(double)sqrt((x[i]-nx)*(x[i]-nx)+(y[i]-ny)*(y[i]-ny))*w[i];
    return res;
}
inline void SA(double &X,double &Y)//Simulated Annealing,模拟退火
{
    double tt=3000,res=dis(X,Y);//tt表示变化量,res表示当前代价
    while(tt>0.000000000000001)
    {
        double nx=X+(rand()*2-RAND_MAX)*tt,ny=Y+(rand()*2-RAND_MAX)*tt,new_res=dis(nx,ny);//计算出新的坐标以及新的代价
        if(new_res<res||exp((res-new_res)/tt)*RAND_MAX>rand()) res=new_res,X=nx,Y=ny;//如果新的代价小于当前代价,或在一定的几率下,更新当前状态
        tt*=delta;//将变化量减小,是一个模拟物理学上的退火操作的过程
    }
}
int main()
{
    srand(time(NULL)),srand(rand()),srand(rand());
    register int i;
    for(read(n),i=1;i<=n;++i) read(x[i]),read(y[i]),read(w[i]);
    double ans_x=0.0,ans_y=0.0;
    for(i=1;i<=10;++i) SA(ans_x,ans_y);//模拟退火10次
    return printf("%.3lf %.3lf",ans_x,ans_y),0;
}

猜你喜欢

转载自blog.csdn.net/chenxiaoran666/article/details/81263837
今日推荐