Country Meow - Gym 101981D - Virtual Judge (vjudge.net)
题意:
给定三维坐标中的n个点,求一个点使得其到最远点的距离最小。
以这个点为圆心,这个点到最远点的距离为半径的球就是最小的能够覆盖所有点集的球。也就是找最小覆盖球。
做法:
考虑一种逼近算法,我们想要从原点坐标p开始逼近,逼近到我们想要的圆心坐标。可以这样操作:
每次找到距离p点的最远点,设p点到当前最远点的距离为len,更新p点,让p点走len的一部分长度逼近当前最远点,然后让这个部分越来越小,比如第一次走len的三分之二,第二次走len的三分之一。降为到二维平面模拟一下过程如下:
我们从黑点(坐标原点)开始逼近,
第一次逼近:最远点是1,走一部分长度。
第二次逼近:最远点是3,走一小部分长度。
第三次逼近:最远点是2,走更小一部分长度。
如此逼近下去,我们就能逼近到我们想要的圆心坐标:
附上代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e3 + 10;
struct node
{
double x, y, z;
} point[N];
double dist(node a, node b)
{
return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y) + (a.z - b.z) * (a.z - b.z));
}
void solve()
{
int n;
cin >> n;
for (int i = 1; i <= n; i++)
cin >> point[i].x >> point[i].y >> point[i].z;
double step = 100; // 每次走的步数大小的指标
double rate = 0.99999; // 每次走的步数大小的减小指标
double eps = 1e-3; // 精度
node p = {0, 0, 0}; // 从原点坐标开始逼近
double ans = 1e9; // 每次找到距离p点的最远点,一直取最小值
int idx = 0; // 上一个距离p点的最远点
while (step > eps)
{
for (int i = 1; i <= n; i++)
{
if (dist(p, point[i]) > dist(p, point[idx])) // 找到当前距离p点最远的点
idx = i;
}
double len = dist(p, point[idx]); // 距离p点最远的点的长度
ans = min(ans, len);
p.x += (point[idx].x - p.x) * (step / len);
p.y += (point[idx].y - p.y) * (step / len);
p.z += (point[idx].z - p.z) * (step / len);
// 走的步长是p点距离当前最远点的一部分
step *= rate;
// step逐渐减小,每次走的步长从p点距离当前最远点的一部分逐渐变成一小部分
}
cout << ans << endl;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t;
// cin >> t;
t = 1;
while (t--)
solve();
}
其实这种算法叫做模拟退火算法,有关模拟退火算法:模拟退火算法_模拟退火算法路径规划的缺点-CSDN博客
这里的step相当于初始温度,rate相当于降温速率,eps相当于温度下限。