3028. 最小圆覆盖
题目链接
3028. 最小圆覆盖
在一个二维平面上给定 \(N\) 个点,请你画出一个最小的能够包含所有点的圆。
圆的边上的点视作在圆的内部。
输入格式
第一行包含一个整数 \(N\)。
接下来 \(N\) 行,每行包含两个实数,表示一个点的坐标 \((X_i,Y_i)\)。
输出格式
第一行输出圆的半径。
第二行输出圆心的坐标。
结果保留 \(10\) 位小数。
数据范围
\(2 \le N \le 10^5\),
\(-10000.0 \le X_i,Y_i \le 10000.0\)
输入样例:
6
8.0 9.0
4.0 7.5
1.0 2.0
5.1 8.7
9.0 2.0
4.5 1.0
输出样例:
5.0000000000
5.0000000000 5.0000000000
解题思路
最小圆覆盖
最小圆覆盖是一种随机化算法,其有两条重要的性质:
-
最小覆盖圆唯一
-
若点 \(P\) 不在点集 \(S\) 的最小覆盖圆内部,则点 \(P\) 必在 \(P\cup S\) 的最小覆盖圆边上
利用这两条性质可以采用随机增量算法解决最小覆盖圆问题,即假设现在已经求出了 \(1\sim i-1\) 的最小覆盖圆,且此时 \(i\) 这个点在圆外,则 \(i\) 必定在前 \(i\) 个点构成的最小覆盖圆上,即找到了前 \(i\) 个点构成的最小覆盖圆的其中一个点,还需要找到另外两个点,对于前 \(i\) 个点,假设已经求出 \(1\sim j-1\) 且 \(i\) 这个点在边上的最小覆盖圆,此时 \(j\) 在这样的最小覆盖圆外部,不难证明此时 \(j\) 在前 \(j\) 个点且 \(i\) 在边上的最小覆盖圆的边上,这时找到了两个点 \(i,j\),同理一开始先构建这两个点的最小覆盖圆,对于前 \(j\) 个点,假设已经求出 \(1\sim k-1\) 且 \(i,j\) 这两个点在边上的最小覆盖圆,此时 \(k\) 在这样的最小覆盖圆外部,这时找到三个点 \(i,j,k\) 即可构成前 \(i\) 个点的最小覆盖圆
时间复杂度证明:
对于内层循环来说,其时间复杂度:\(O(n)\),这三个点是关键点,对于中层循环,其有 \(\frac{3}{n}\) 的概率到达内层循环,即时间复杂度为 \(O(n+\frac{3}{n}\times n)\),外层循环同理,故:
- 时间复杂度:\(O(n)\)
代码
// Problem: 最小圆覆盖
// Contest: AcWing
// URL: https://www.acwing.com/problem/content/3031/
// Memory Limit: 64 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
// %%%Skyqwq
#include <bits/stdc++.h>
//#define int long long
#define help {cin.tie(NULL); cout.tie(NULL);}
#define pb push_back
#define fi first
#define se second
#define mkp make_pair
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }
template <typename T> void inline read(T &x) {
int f = 1; x = 0; char s = getchar();
while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
x *= f;
}
const int N=1e5+5;
const double eps=1e-12,pi=acos(-1);
typedef pair<double,double> PDD;
int n;
PDD a[N];
struct Circle
{
PDD p;
double r;
};
PDD operator+(PDD a,PDD b)
{
return {a.fi+b.fi,a.se+b.se};
}
PDD operator-(PDD a,PDD b)
{
return {a.fi-b.fi,a.se-b.se};
}
PDD operator*(PDD a,double t)
{
return {a.fi*t,a.se*t};
}
double operator*(PDD a,PDD b)
{
return a.fi*b.se-b.fi*a.se;
}
PDD operator/(PDD a,double t)
{
return {a.fi/t,a.se/t};
}
int sign(double x)
{
if(fabs(x)<eps)return 0;
if(x<0)return -1;
return 1;
}
int dcmp(double x,double y)
{
if(fabs(x-y)<0)return 0;
if(x<y)return -1;
return 1;
}
double get_dist(PDD a,PDD b)
{
return sqrt((a.fi-b.fi)*(a.fi-b.fi)+(a.se-b.se)*(a.se-b.se));
}
PDD rotate(PDD a,double angle)
{
return {a.fi*cos(angle)+a.se*sin(angle),-a.fi*sin(angle)+a.se*cos(angle)};
}
pair<PDD,PDD> get_line(PDD a,PDD b)
{
return {(a+b)/2,rotate(b-a,pi/2)};
}
PDD get_line_intersection(PDD p,PDD v,PDD q,PDD w)
{
PDD u=p-q;
double t=w*u/(v*w);
return p+v*t;
}
Circle get_circle(PDD a,PDD b,PDD c)
{
auto u=get_line(a,b),v=get_line(a,c);
PDD p=get_line_intersection(u.fi,u.se,v.fi,v.se);
return {p,get_dist(p,a)};
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%lf%lf",&a[i].fi,&a[i].se);
random_shuffle(a+1,a+1+n);
Circle c={a[1],0};
for(int i=2;i<=n;i++)
if(dcmp(c.r,get_dist(c.p,a[i]))<0)
{
c={a[i],0};
for(int j=1;j<i;j++)
if(dcmp(c.r,get_dist(c.p,a[j]))<0)
{
c={(a[i]+a[j])/2,get_dist(a[i],a[j])/2};
for(int k=1;k<j;k++)
if(dcmp(c.r,get_dist(c.p,a[k]))<0)
{
c=get_circle(a[i],a[j],a[k]);
}
}
}
printf("%.10lf\n%.10lf %.10lf",c.r,c.p.fi,c.p.se);
return 0;
}