[BZOJ 3680] 吊打XXX 【模拟退火】
题目链接:BZOJ - 3680
题目分析
这道题是SLYZ的神犇把JSOI的平衡点那道题改了一下题面变成了吊打GTY神犇..Orz
第一次写模拟退火,只能照着别人的代码写,我看的是PoPoQQQ神犇的代码,于是我基本上完全照着写的,代码没什么区别= =
首先是模型的转化,一看这道题目是神奇的物理题,我完全就不会啊。
搜题解,题解是这样的:根据“一切自然变化进行的方向都是使能量降低,因为能量较低的状态比较稳定”的基本物理原理,这个绳结的移动趋势是使整个系统的总能量降低。
这个系统是一个机械系统,每个状态可以看做只具有重力势能,那么为了使所有中重物重力势能的和最小,我们就要使 sigma(d[i] * w[i]) 最大。
其中 d[i] 是绳结到重物 i 的距离,w[i] 是重物 i 的重力。
这样,就是求一个点,到一些顶点的加权距离和最小,是一个广义费马点问题。
可以使用模拟退火算法来求解。
模拟退火算法的步骤:
选定一个初始状态(比如选定所有点坐标的平均数),选定一个初始温度 T 。
当温度大于一个边界值时:
{
随机变化坐标,变化幅度为 T 。
计算新解与当前解的差 DE。
如果新解比当前解优(DE > 0),就用新解替换当前解。
否则以 exp(DE / T) 的概率用新解替换当前解。
温度乘上一个小于1的系数,即降温。
}
这样,随着温度不断降低,变化幅度也越来越小,接受一个更劣的解的概率也越来越小。
最终求出一个坐标。
为了使答案尽量优,再以这个求出的坐标为基准向四周随机移动若干次,取最优解。
代码
#include <iostream> #include <cstdlib> #include <cstring> #include <cstdio> #include <cmath> #include <algorithm> using namespace std; typedef double DB; const int MaxN = 10000 + 5; int n; DB Ansx, Ansy, Dis; DB X[MaxN], Y[MaxN], W[MaxN]; inline DB Sqr(DB x) {return x * x;} inline DB d(DB x, DB y, DB xx, DB yy) { return sqrt(Sqr(x - xx) + Sqr(y - yy)); } DB Calc(DB x, DB y) { DB ret = 0; for (int i = 1; i <= n; ++i) ret += W[i] * d(x, y, X[i], Y[i]); if (ret < Dis) { Dis = ret; Ansx = x; Ansy = y; } return ret; } inline DB Rand() { return (DB)(rand() % 10000) / 10000.0; } void SA() { DB DE, T = 100000; DB Nowx, Nowy, Nx, Ny; Nowx = Ansx; Nowy = Ansy; while (T > 0.001) { Nx = Nowx + T * (Rand() * 2 - 1); Ny = Nowy + T * (Rand() * 2 - 1); DE = Calc(Nowx, Nowy) - Calc(Nx, Ny); if (DE > 0 || exp(DE / T) > Rand()) { Nowx = Nx; Nowy = Ny; } T *= 0.97; } for (int i = 1; i <= 1000; ++i) { Nx = Ansx + T * (Rand() * 2 - 1); Ny = Ansy + T * (Rand() * 2 - 1); Calc(Nx, Ny); } } int main() { srand(804589); 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 /= (DB)n; Ansy /= (DB)n; Dis = Calc(Ansx, Ansy); SA(); printf("%.3lf %.3lf\n", Ansx, Ansy); return 0; }