题解:P13468 [GCJ 2008 #2] Star Wars
abc114514avdf · · 题解
P13468 Star Wars 题解
思路
模拟退火。
题解区清一色的推式子+二分/三分,但是模拟退火也可以通过此题,并且好想,为什么没人用呢?
不知道模拟退火是什么的可以看这里。
首先,我们注意到答案是一个三元函数,所以我们可以选定一个初始点,然后退火逐渐降温,每次随机一个模长小于温度的向量,尝试移动答案,如果更优就移动。但是这样精度不高,所以再随机几轮,取温度恒为
实测跑得飞快。
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const double t = 0.95, eps = 6e-4;
int T, n, x[1005], y[1005], z[1005], p[1005];
pair<double, pair<double, double> > rdm()
{
double x = rand() * 1145141919, y = rand() * 1145141919, z = rand() * 1145141919; // 利用自然溢出均摊值域
double l = sqrt(x * x + y * y + z * z);
return {x / l, {y / l, z / l}};
}
double slv(double x0, double y0, double z0)
{
double ans = 0;
for (int i = 1; i <= n; i++)
{
ans = max(ans, (abs(x[i] - x0) + abs(y[i] - y0) + abs(z[i] - z0)) * 1.0 / p[i]);
}
return ans;
}
int main()
{
srand(time(0));
cin >> T;
for (int q = 1; q <= T; q++)
{
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> x[i] >> y[i] >> z[i] >> p[i];
}
double nx = 0, ny = 0, nz = 0;
for (int _ = 1; _ <= 200; _++)
{
double tp = 5e4;
while (tp > eps)
{
auto p = rdm();
double l = 10000ll * rand() * rand() % (ll)(tp * 10000) / 10000.0, ans = slv(nx + p.first * l, ny + p.second.first * l, nz + p.second.second * l);
if (ans < slv(nx, ny, nz)) nx += p.first * l, ny += p.second.first * l, nz += p.second.second * l;
tp *= t;
}
}
for (int _ = 1; _ <= 400; _++)
{
auto p = rdm();
double l = 5e-7, ans = slv(nx + p.first * l, ny + p.second.first * l, nz + p.second.second * l);
if (ans < slv(nx, ny, nz)) nx += p.first * l, ny += p.second.first * l, nz += p.second.second * l;
}
printf("Case #%d: %.8f\n", q, slv(nx, ny, nz));
}
return 0;
}