Luogu P1337 [JSOI2004]平衡点 / 吊打XXX

一道入门模拟退火的经典题,还是很考验RP的

首先我们发现神TM这道题又和物理扯上了关系,其实是一道求广义费马点的题目

首先我们可以根据物理知识得到,当系统处于平衡状态时,系统的总能量最小

又此时系统的总能量是等于各个物体的重力势能,在质量一定时,即要求物体离地最近,离桌子最远

那么,也就是绳子在桌子上的距离尽量的小,即要求\(\sum_{i=1}^n m_i\cdot dist_{i,x}\)最小

(以上物理部分推导摘于pym‘s blog

然后考虑退火,我们先选取一个初始位置(一般取所有点坐标的平均数方便收敛)

然后每次退火时给坐标随机一个增量(要随温度降低而减少,并注意需要取的),并计算新的解的答案

同时按照一般的模拟退火流程考虑是否接受解并且降温即可

PS:本题极大的考验调参能力,本人Luogu由于机子快,交了几发就A了。TM的BZOJ老爷机一直在WA和TLE直接徘徊,最后好像9900+MSA了(时限10S)。

CODE

#include<cstdio>
#include<cstdlib>
#include<cctype>
#include<cmath>
#include<ctime>
using namespace std;
typedef double DB;
const int N=1005;
const DB EPS=1e-30,dlt=0.981;
struct data
{
    int x,y,w;
}a[N];
int n; DB ans_x,ans_y,ave_x,ave_y,ans;
inline char tc(void)
{
    static char fl[100000],*A=fl,*B=fl;
    return A==B&&(B=(A=fl)+fread(fl,1,100000,stdin),A==B)?EOF:*A++;
}
inline void read(int &x)
{
    x=0; char ch; int flag=1; while (!isdigit(ch=tc())) flag=ch^'-'?1:-1;
    while (x=(x<<3)+(x<<1)+ch-'0',isdigit(ch=tc())); x*=flag; 
}
inline DB calc(DB x,DB y)
{
    register int i; DB tot=0;
    for (i=1;i<=n;++i)
    tot+=(DB)sqrt((x-a[i].x)*(x-a[i].x)+(y-a[i].y)*(y-a[i].y))*a[i].w;
    return tot;
}
inline void Simulate_Anneal(DB x,DB y)
{
    DB T=500,res=calc(x,y); 
    for (;T>EPS;T*=dlt)
    {
        DB xx=x+(rand()*2-RAND_MAX)*T,yy=y+(rand()*2-RAND_MAX)*T,now=calc(xx,yy);
        if (now<ans) ans=now,ans_x=xx,ans_y=yy;
        if (now<res||exp((res-now)/T)>(DB)rand()/RAND_MAX) res=now,x=xx,y=yy;
    }
}
int main()
{
    //freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
    register int i,t=50; srand(time(0)); read(n);
    for (i=1;i<=n;++i)
    {
        read(a[i].x); read(a[i].y); read(a[i].w);
        ave_x+=a[i].x; ave_y+=a[i].y;
    }
    ans_x=ave_x=(DB)ave_x/n; ans_y=ave_y=(DB)ave_y/n; ans=calc(ans_x,ans_y);
    while (t--) Simulate_Anneal(ave_x,ave_y);
    return printf("%.3lf %.3lf",ans_x,ans_y),0;
}

猜你喜欢

转载自www.cnblogs.com/cjjsb/p/9545110.html
今日推荐