Skip to content

Commit ed33505

Browse files
authored
Add minimum enclosing circle article (#1498)
New original article
1 parent b453603 commit ed33505

File tree

3 files changed

+211
-0
lines changed

3 files changed

+211
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ Compiled pages are published at [https://cp-algorithms.com/](https://cp-algorith
3030

3131
### New articles
3232

33+
- (19 August 2025) [Minimum Enclosing Circle](https://cp-algorithms.com/geometry/enclosing-circle.html)
3334
- (21 May 2025) [Simulated Annealing](https://cp-algorithms.com/num_methods/simulated_annealing.html)
3435
- (12 July 2024) [Manhattan distance](https://cp-algorithms.com/geometry/manhattan-distance.html)
3536
- (8 June 2024) [Knapsack Problem](https://cp-algorithms.com/dynamic_programming/knapsack.html)

src/geometry/enclosing-circle.md

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
---
2+
tags:
3+
- Original
4+
---
5+
6+
# Minimum Enclosing Circle
7+
8+
Consider the following problem:
9+
10+
!!! example "[Library Checker - Minimum Enclosing Circle](https://judge.yosupo.jp/problem/minimum_enclosing_circle)"
11+
12+
You're given $n \leq 10^5$ points $p_i=(x_i, y_i)$.
13+
14+
For each $p_i$, find whether it lies on the circumference of the minimum enclosing circle of $\{p_1,\dots,p_n\}$.
15+
16+
Here, by the minimum enclosing circle (MEC) we mean a circle with minimum possible radius that contains all the $n$ p, inside the circle or on its boundary. This problem has a simple randomized solution that, on first glance, looks like it would run in $O(n^3)$, but actually works in $O(n)$ expected time.
17+
18+
To better understand the reasoning below, we should immediately note that the solution to the problem is unique:
19+
20+
??? question "Why is the MEC unique?"
21+
22+
Consider the following setup: Let $r$ be the radius of the MEC. We draw a circle of radius $r$ around each of the p $p_1,\dots,p_n$. Geometrically, the centers of circles that have radius $r$ and cover all the points $p_1,\dots,p_n$ form the intersection of all $n$ circles.
23+
24+
Now, if the intersection is just a single point, this already proves that it is unique. Otherwise, the intersection is a shape of non-zero area, so we can reduce $r$ by a tiny bit, and still have non-empty intersection, which contradicts the assumption that $r$ was the minimum possible radius of the enclosing circle.
25+
26+
With a similar logic, we can also show the uniqueness of the MEC if we additionally demand that it passes through a given specific point $p_i$ or two points $p_i$ and $p_j$ (it is also unique because its radius uniquely defines it).
27+
28+
Alternatively, we can also assume that there are two MECs, and then notice that their intersection (which contains p $p_1,\dots,p_n$ already) must have a smaller diameter than initial circles, and thus can be covered with a smaller circle.
29+
30+
## Welzl's algorithm
31+
32+
For brevity, let's denote $\operatorname{mec}(p_1,\dots,p_n)$ to be the MEC of $\{p_1,\dots,p_n\}$, and let $P_i = \{p_1,\dots,p_i\}$.
33+
34+
The algorithm, initially [proposed](https://doi.org/10.1007/BFb0038202) by Welzl in 1991, goes as follows:
35+
36+
1. Apply a random permutation to the input sequence of points.
37+
2. Maintain the current candidate to be the MEC $C$, starting with $C = \operatorname{mec}(p_1, p_2)$.
38+
3. Iterate over $i=3..n$ and check if $p_i \in C$.
39+
1. If $p_i \in C$ it means that $C$ is the MEC of $P_i$.
40+
2. Otherwise, assign $C = \operatorname{mec}(p_i, p_1)$ and iterate over $j=2..i$ and check if $p_j \in C$.
41+
1. If $p_j \in C$, then $C$ is the MEC of $P_j$ among circles that pass through $p_i$.
42+
2. Otherwise, assign $C=\operatorname{mec}(p_i, p_j)$ and iterate over $k=1..j$ and check if $p_k \in C$.
43+
1. If $p_k \in C$, then $C$ is the MEC of $P_k$ among circles that pass through $p_i$ and $p_j$.
44+
2. Otherwise, $C=\operatorname{mec}(p_i,p_j,p_k)$ is the MEC of $P_k$ among circles that pass through $p_i$ and $p_j$.
45+
46+
We can see that each level of nestedness here has an invariant to maintain (that $C$ is the MEC among circles that also pass through additionally given $0$, $1$ or $2$ points), and whenever the inner loop closes, its invariant becomes equivalent to the invariant of the current iteration of its parent loop. This, in turn, ensures the _correctness_ of the algorithm as a whole.
47+
48+
Omitting some technical details, for now, the whole algorithm can be implemented in C++ as follows:
49+
50+
```cpp
51+
struct point {...};
52+
53+
// Is represented by 2 or 3 points on its circumference
54+
struct mec {...};
55+
56+
bool inside(mec const& C, point p) {
57+
return ...;
58+
}
59+
60+
// Choose some good generator of randomness for the shuffle
61+
mt19937_64 gen(...);
62+
mec enclosing_circle(vector<point> &p) {
63+
ranges::shuffle(p, gen);
64+
auto C = mec{p[0], p[1]};
65+
for(int i = 0; i < n; i++) {
66+
if(!inside(C, p[i])) {
67+
C = mec{p[i], p[0]};
68+
for(int j = 0; j < i; j++) {
69+
if(!inside(C, p[j])) {
70+
C = mec{p[i], p[j]};
71+
for(int k = 0; k < j; k++) {
72+
if(!inside(C, p[k])) {
73+
C = mec{p[i], p[j], p[k]};
74+
}
75+
}
76+
}
77+
}
78+
}
79+
}
80+
return C;
81+
}
82+
```
83+
84+
Now, it is to be expected that checking that a point $p_i$ is inside the MEC of $2$ or $3$ points can be done in $O(1)$ (we will discuss this later on). But even then, the algorithm above looks as if it would take $O(n^3)$ in the worst case just because of all the nested loops. So, how come we claimed the linear expected runtime? Let's figure out!
85+
86+
### Complexity analysis
87+
88+
For the inner-most loop (over $k$), clearly its expected runtime is $O(j)$ operations. What about the loop over $j$?
89+
90+
It only triggers the next loop if $p_j$ is on the boundary of the MEC of $P_j$ that also passes through point $i$, _and removing $p_j$ would further shrink the circle_. Of all points in $P_j$ there can only be at most $2$ points with such property, because if there are more than $2$ points from $P_j$ on the boundary, it means that after removing any of them, there will still be at least $3$ points on the boundary, sufficient to uniquely define the circle.
91+
92+
In other words, after initial random shuffle, there is at most $\frac{2}{j}$ probability that we get one of the at most two unlucky points as $p_j$. Summing it up over all $j$ from $1$ to $i$, we get the expected runtime of
93+
94+
$$
95+
\sum\limits_{j=1}^i \frac{2}{j} \cdot O(j) = O(i).
96+
$$
97+
98+
In exactly same fashion we can now also prove that the outermost loop has expected runtime of $O(n)$.
99+
100+
### Checking that a point is in the MEC of 2 or 3 points
101+
102+
Let's now figure out the implementation detail of `point` and `mec`. In this problem, it turns out to be particularly useful to use [std::complex](https://codeforces.com/blog/entry/22175) as a class for points:
103+
104+
```cpp
105+
using ftype = int64_t;
106+
using point = complex<ftype>;
107+
```
108+
109+
As a reminder, a complex number is a number of type $x+yi$, where $i^2=-1$ and $x, y \in \mathbb R$. In C++, such complex number is represented by a 2-dimensional point $(x, y)$. Complex numbers already implement basic component-wise linear operations (addition, multiplication by a real number), but also their multiplication and division carry certain geometric meaning.
110+
111+
Without going in too much detail, we will note the most important property for this particular task: Multiplying two complex numbers adds up their polar angles (counted from $Ox$ counter-clockwise), and taking a conjugate (i.e. changing $z=x+yi$ into $\overline{z} = x-yi$) multiplies the polar angle with $-1$. This allows us to formulate some very simple criteria for whether a point $z$ is inside the MEC of $2$ or $3$ specific points.
112+
113+
#### MEC of 2 points
114+
115+
For $2$ points $a$ and $b$, their MEC is simply the circle centered at $\frac{a+b}{2}$ with the radius $\frac{|a-b|}{2}$, in other words the circle that has $ab$ as a diameter. To check if $z$ is inside this circle we simply need to check that the angle between $za$ and $zb$ is not acute.
116+
117+
<center>
118+
<img src="https://upload.wikimedia.org/wikipedia/commons/8/8e/Diameter_angles.svg">
119+
<br>
120+
<i>Inner angles are obtuse, external angles are acute and angles on the circumference are right</i>
121+
</center>
122+
123+
Equivalently, we need to check that
124+
125+
$$
126+
I_0=(b-z)\overline{(a-z)}
127+
$$
128+
129+
doesn't have a positive real coordinate (corresponding to points that have a polar angle between $-90^\circ$ and $90^\circ$).
130+
131+
#### MEC of 3 points
132+
133+
Adding $z$ to the triangle $abc$ will make it a quadrilateral. Consider the following expression:
134+
135+
$$
136+
\angle azb + \angle bca
137+
$$
138+
139+
In a [cyclic quadrilateral](https://en.wikipedia.org/wiki/Cyclic_quadrilateral), if $c$ and $z$ are from the same side of $ab$, then the angles are equal, and will ad up to $0^\circ$ when summed up signed (i.e. positive if counter-clockwise and negative if clockwise). Correspondingly, if $c$ and $z$ are on the opposite sides, the angles will add up to $180^\circ$.
140+
141+
<center>
142+
<img src="https://upload.wikimedia.org/wikipedia/commons/3/30/Opposing_inscribed_angles.svg">
143+
<br>
144+
<i>Adjacent inscribed angles are same, opposing angles complement to 180 degrees</i>
145+
</center>
146+
147+
In terms of complex numbers, we can note that $\angle azb$ is the polar angle of $(b-z)\overline{(a-z)}$ and $\angle bca$ is the polar angle of $(a-c)\overline{(b-c)}$. Thus, we can conclude that $\angle azb + \angle bca$ is the polar angle of
148+
149+
$$
150+
I_1 = (b-z) \overline{(a-z)} (a-c) \overline{(b-c)}
151+
$$
152+
153+
If the angle is $0^\circ$ or $180^\circ$, it means that the imaginary part of $I_1$ is $0$, otherwise we can deduce whether $z$ is inside or outside of the enclosing circle of $abc$ by checking the sign of the imaginary part of $I_1$. Positive imaginary part corresponds to positive angles, and negative imaginary part corresponds to negative angles.
154+
155+
But which one of them means that $z$ is inside or outside of the circle? As we already noticed, having $z$ inside the circle generally increases the magnitude of $\angle azb$, while having it outside the circle decreases it. As such, we have the following 4 cases:
156+
157+
1. $\angle bca > 0^\circ$, $c$ on the same side of $ab$ as $z$. Then, $\angle azb < 0^\circ$, and $\angle azb + \angle bca < 0^\circ$ for points inside the circle.
158+
3. $\angle bca < 0^\circ$, $c$ on the same side of $ab$ as $z$. Then, $\angle azb > 0^\circ$, and $\angle azb + \angle bca > 0^\circ$ for points inside the circle.
159+
2. $\angle bca > 0^\circ$, $c$ on the opposite side of $ab$ to $z$. Then, $\angle azb > 0^\circ$ and $\angle azb + \angle bca > 180^\circ$ for points inside the circle.
160+
4. $\angle bca < 0^\circ$, $c$ on the opposite side of $ab$ to $z$. Then, $\angle azb < 0^\circ$ and $\angle azb + \angle bca < 180^\circ$ for points inside the circle.
161+
162+
In other words, if $\angle bca$ is positive, points inside the circle will have $\angle azb + \angle bca < 0^\circ$, otherwise they will have $\angle azb + \angle bca > 0^\circ$, assuming that we normalize the angles between $-180^\circ$ and $180^\circ$. This, in turn, can be checked by the signs of imaginary parts of $I_2=(a-c)\overline{(b-c)}$ and $I_1 = I_0 I_2$.
163+
164+
**Note**: As we multiply four complex numbers to get $I_1$, the intermediate coefficients can be as large as $O(A^4)$, where $A$ is the largest coordinate magnitude in the input. On the bright side, if the input is integer, both checks above can be done fully in integers.
165+
166+
#### Implementation
167+
168+
Now, to actually implement the check, we should first decide how to represent the MEC. As our criteria work with the points directly, a natural and efficient way to do this is to say that MEC is directly represented as a pair or triple of points that defines it:
169+
170+
```cpp
171+
using mec = variant<
172+
array<point, 2>,
173+
array<point, 3>
174+
>;
175+
```
176+
177+
Now, we can use `std::visit` to efficiently deal with both cases in accordance with criteria above:
178+
179+
```cpp
180+
/* I < 0 if z inside C,
181+
I > 0 if z outside C,
182+
I = 0 if z on the circumference of C */
183+
ftype indicator(mec const& C, point z) {
184+
return visit([&](auto &&C) {
185+
point a = C[0], b = C[1];
186+
point I0 = (b - z) * conj(a - z);
187+
if constexpr (size(C) == 2) {
188+
return real(I0);
189+
} else {
190+
point c = C[2];
191+
point I2 = (a - c) * conj(b - c);
192+
point I1 = I0 * I2;
193+
return imag(I2) < 0 ? -imag(I1) : imag(I1);
194+
}
195+
}, C);
196+
}
197+
198+
bool inside(mec const& C, point p) {
199+
return indicator(C, p) <= 0;
200+
}
201+
202+
```
203+
204+
Now, we can finally ensure that everything works by submitting the problem to the Library Checker: [#308668](https://judge.yosupo.jp/submission/308668).
205+
206+
## Practice problems
207+
208+
- [Library Checker - Minimum Enclosing Circle](https://judge.yosupo.jp/problem/minimum_enclosing_circle)
209+
- [BOI 2002 - Aliens](https://www.spoj.com/problems/ALIENS)

src/navigation.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ search:
145145
- [Vertical decomposition](geometry/vertical_decomposition.md)
146146
- [Half-plane intersection - S&I Algorithm in O(N log N)](geometry/halfplane-intersection.md)
147147
- [Manhattan Distance](geometry/manhattan-distance.md)
148+
- [Minimum Enclosing Circle](geometry/enclosing-circle.md)
148149
- Graphs
149150
- Graph traversal
150151
- [Breadth First Search](graph/breadth-first-search.md)

0 commit comments

Comments
 (0)