Luckyleaves's Blog

stay hungry,stay foolish greedy and lucky

最小圆覆盖

简介

相关性质

  • 最小覆盖圆是唯一的。
  • 若 $P$ 不在 $S$ 的最小覆盖圆的内部, 则 $P$ 一定在 ${P}\cup S$ 的最小覆盖圆的边上。

推导

由上面的性质我,如果前 $i - 1$ 个点的最小圆覆盖是 $C$ 那么我们可以得到:

  • 如果第 $i$ 个点在圆上的话,那么前$i - 1$ 个点的最小圆覆盖是 $C$。
  • 如果不在,那么 $i$ 一定在前 $i$ 个点的最小覆盖圆上。
  • 因此,我们固定 $i$,以 $P_i$ 为圆心,以0为半径,继续寻找还有那两个点在前 $i$ 个点的最小圆覆盖上。
  • 从 1 到 $i - 1$ 枚举 $j$ ,找到第一个点 $j$ 不在 $C$ 上,当前圆心就为 $\frac{P_i + P_j} 2$,半径就为 $ |P_i P_j|$。
  • 紧接着再固定圆心,继续不停寻找第三个点 $P_k$,根据三点固定一圆,$C$ 即可固定。

求解

  • 将点随机化。
1
2
3
4
5
6
7
8
9
10
11
for(int i = 2;i <= n;i ++)
if(i不在圆内则i必须在1~i的圆的边上)
{
// 插入i,重构圆
遍历圆
for(int j = 1;j < i;j ++)
if(j不在圆内,则j就必须在1~j - 1和i,且i在圆边上的最小圆的边上)
圆<-以Pi,Pj为直径的圆
for(int k = 1;k < j;k ++)
if(k不在圆内,则j就必须在1~k - 1和i,j,且i,j在圆边上的最小圆的边上)
}

外接圆的补充说明

初中的中垂线确定圆心的方法给予我们启示,我们需要以下工具:

  • 求得两个向量的中点。
  • 将一个向量旋转 $90^\circ$。
  • 用一点和一条向量确定一条直线。
  • 求两条直线的交点。

第一个任务,将向量直接 $/2$。

第二个任务,$rotate$ 函数即可解决。

第三个任务,不说了。

第四个任务,根据向量的叉乘我们可以知道

image

用 $O = I + Vt$,$(I + Vt - P) * W = 0$ 解得
$$
\begin{align}
t &= \frac {(P - I) * W}{V*W} \\
O &= I + Vt
\end{align}
$$
由此,圆心即求出来了。

例题

P1742 最小圆覆盖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
#include<bits/stdc++.h>
#define x first
#define y second
#define PII pair<double, double>
using namespace std;
const int N = 1e5 + 5;
const double eps = 1e-12;
const double PI = acos(-1);
int n;
PII q[N];
struct Circle{
PII p;
double r;
};
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) < eps) return 0;
if(x < y) return -1;
return 1;
}
// 基本运算
PII operator- (PII a, PII b)
{
return {a.x - b.x, a.y - b.y};
}
PII operator+ (PII a, PII b)
{
return {a.x + b.x, a.y + b.y};
}
PII operator* (PII a, double b)
{
return {a.x * b, a.y * b};
}
PII operator/ (PII a, double b)
{
return {a.x / b, a.y / b};
}
double operator* (PII a, PII b)
{
return a.x * b.y - a.y * b.x;
}

PII rotate(PII a, double b) // 将向量a旋转b角度
{
return {a.x * cos(b) + a.y * sin(b), -a.x * sin(b) + a.y * cos(b)};
}
double get_dist(PII a, PII b) // 计算a到 b的距离
{
double dx = a.x - b.x;
double dy = a.y - b.y;
return sqrt(dx * dx + dy * dy);
}

PII get_line_intersection(PII p, PII v, PII q, PII w) // 求交点
{
PII u = p - q;
double t = w * u / (v * w);
return p + v * t;
}
pair<PII, PII> get_line(PII a, PII b) // 求中垂线
{
return {(a + b) / 2, rotate(b - a, PI / 2)};
}
Circle get_circle(PII a, PII b, PII c) // 求圆心
{
pair<PII, PII> u = get_line(a, b), v = get_line(a, c);
PII p = get_line_intersection(u.x, u.y, v.x, v.y);
return {p, get_dist(p, a)};
}
int main()
{
scanf("%d", &n);
for(int i = 0;i < n;i ++) scanf("%lf%lf", &q[i].x, &q[i].y);
random_shuffle(q, q + n);
Circle c({q[0], 0});
for(int i = 1;i < n;i ++)
if(dcmp(c.r, get_dist(c.p, q[i])) < 0)
{
c = {q[i], 0};
for(int j = 0;j < i;j ++)
{
if(dcmp(c.r, get_dist(c.p, q[j])) < 0)
{
c = {(q[i] + q[j]) / 2, get_dist(q[i], q[j]) / 2};
for(int k = 0;k < j;k ++)
{
if(dcmp(c.r, get_dist(c.p, q[k])) < 0)
c = get_circle(q[i], q[j], q[k]);
}
}
}
}
printf("%.10lf\n", c.r);
printf("%.10lf %.10lf\n", c.p.x, c.p.y);
return 0;
}

P4288 [SHOI2014]信号增幅仪

  • 严格来讲如果知道压缩椭圆,旋转坐标轴的话应该还是算模板题的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
#include<bits/stdc++.h>
#define x first
#define y second
#define PII pair<double, double>
using namespace std;
const int N = 50010 + 5;
const double eps = 1e-12;
const double PI = acos(-1);
int n;
PII q[N];
struct Circle{
PII p;
double r;
};
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) < eps) return 0;
if(x < y) return -1;
return 1;
}
PII operator- (PII a, PII b)
{
return {a.x - b.x, a.y - b.y};
}
PII operator+ (PII a, PII b)
{
return {a.x + b.x, a.y + b.y};
}
PII operator* (PII a, double b)
{
return {a.x * b, a.y * b};
}
PII operator/ (PII a, double b)
{
return {a.x / b, a.y / b};
}
double operator* (PII a, PII b)
{
return a.x * b.y - a.y * b.x;
}
PII rotate(PII a, double b)
{
return {a.x * cos(b) + a.y * sin(b), -a.x * sin(b) + a.y * cos(b)};
}
double get_dist(PII a, PII b)
{
double dx = a.x - b.x;
double dy = a.y - b.y;
return sqrt(dx * dx + dy * dy);
}

PII get_line_intersection(PII p, PII v, PII q, PII w) // 求交点
{
PII u = p - q;
double t = w * u / (v * w);
return p + v * t;
}
pair<PII, PII> get_line(PII a, PII b)
{
return {(a + b) / 2, rotate(b - a, PI / 2)};
}
Circle get_circle(PII a, PII b, PII c)
{
pair<PII, PII> u = get_line(a, b), v = get_line(a, c);
PII p = get_line_intersection(u.x, u.y, v.x, v.y);
return {p, get_dist(p, a)};
}
int main()
{
scanf("%d", &n);
for(int i = 0;i < n;i ++) scanf("%lf%lf", &q[i].x, &q[i].y);
double a, p;
scanf("%lf%lf", &a, &p);
for(int i = 0;i < n;i ++)
{
q[i] = rotate(q[i], a / 180 * PI);
q[i].x /= p;
}
random_shuffle(q, q + n);
Circle c({q[0], 0});
for(int i = 1;i < n;i ++)
if(dcmp(c.r, get_dist(c.p, q[i])) < 0)
{
c = {q[i], 0};
for(int j = 0;j < i;j ++)
if(dcmp(c.r, get_dist(c.p, q[j])) < 0)
{
c = {(q[i] + q[j]) / 2, get_dist(q[i], q[j]) / 2};
for(int k = 0;k < j;k ++)
{
if(dcmp(c.r, get_dist(c.p, q[k])) < 0)
{
c = get_circle(q[i], q[j], q[k]);
}
}
}
}
printf("%.3lf", c.r);
return 0;
}