[Tutorial] Tangents of a Polygon in O(log n)
Difference between en22 and en23, changed 0 character(s)
In one of the last stages of the 3rd Universal Cup, there was a not-so-nice problem (it required using fractions) that involved an algorithm for which I hadn't found a tutorial or much information on popular blogs. This approach was developed by me, and I appreciate any feedback on it or its implementation.↵

https://codeforces.me/gym/105657/problem/C↵

#### Parameters↵
- The polygon is convex↵
- The point is not strictly inside↵

#### What are the tangents to a polygon from a point?↵

The tangents of a polygon with respect to a point are the lines that pass through the point outside the polygon and touch the polygon at exactly one vertex or along one side, without crossing its interior.↵

<img src="/predownloaded/7a/2b/7a2bc71ba65fd9ebcf0df712893bfe96417c8dc6.png" style="width:350px; height:300px;" />↵

#### Why are these useful?↵

- Solve difficult problems↵
- Solve problems related to visibility or trajectory where the polygon cannot be crossed.↵

#### Algorithm↵

To describe the algorithm, only the left tangent will be specified. At the end, it will be indicated what changes with respect to the right tangent.↵

The left tangent can be described using the cross product. For a point $P_i$ and the point $p$ (for which the tangents are computed), it can be considered the tangent if the cross product with respect to points $P_{i+1}$ and $P_{i-1}$ is less than or equal to zero:↵


$$↵
(P_{i} - p) \times (P_{i+1} - p) \leq 0↵
$$↵

$$↵
(P_{i} - p) \times (P_{i-1} - p) \leq 0↵
$$↵

Now, we will analyze the behavior of these signs in the following example.  ↵

<img src="/predownloaded/3d/a3/3da30ef20483807a4de4774405c1ed188eb38e35.png" style="width:500px; height:350px;" />↵

Let us observe that the signs for point C are exactly as we require.  ↵
For points G, F, and E, the second condition is satisfied, and for points A, M, T, U, and V, the first condition is satisfied.  ↵
However, we can notice that it behaves like a unimodal function.↵

Thus, it is enough to find the first point for which the first condition is satisfied (in a counterclockwise direction).  ↵
To do this, we will do two things.↵

- Divide the convex hull into its lower and upper layers.↵
- Divide each layer into two parts to find the left and right tangents.↵

There will be cases where it would not be necessary to use a layer, as in the previous example. Determining when it would be necessary or not would only add more complexity and does not ultimately change the complexity.↵

The same example illustrated on the division.↵

<img src="/predownloaded/e0/e1/e0e159af9209f41e9d3db96982045bf68c341391.png" style="width:500px; height:350px;" />↵

When dividing the convex hull, we must take into account that if it is a single point that is farthest to the left (smallest x), it must be in both layers, and the same applies to the point farthest to the right (largest x).↵

To divide each layer into two parts, a binary search will be used.↵

For the right tangent, we will use these conditions:↵

$$↵
(P_{i} - p) \times (P_{i+1} - p) \geq 0↵
$$↵

$$↵
(P_{i} - p) \times (P_{i-1} - p) \geq 0↵
$$↵


Finally, the complexity analysis: a total of six binary searches will be performed, one for each layer and one for each of the four divisions of the polygon, resulting in $O(\log n)$.↵

<spoiler summary="Implementation">↵
```cpp↵
#include <bits/stdc++.h>↵
using namespace std;↵

// Holi c:↵

vector<point> tangentsPointPolygon(const vector<point> & P, const vector<vector<point>> & Ps, const point & p){↵
int n = P.size(), m = Ps[0].size(), k = Ps[1].size();↵

int lk = m; if(Ps[0][m - 1] == Ps[1][0]) lk--; ↵

auto tang = [&](int l, int r, ld w, int kl) -> int {↵
int res = l;↵
        while(l <= r){↵
int m = (l + r) / 2;↵
ld a = (P[(m + kl) % n] - p).cross(P[(m + 1 + kl) % n] - p) * w, b = (P[(m + kl) % n] - p).cross(P[(m - 1 + n + kl) % n] - p) * w;↵
if(geq(a, 0) && geq(b, 0)) return m;↵
if(geq(a, 0)) r = m - 1, res = m;↵
else l = m + 1;↵
        }↵
        return res;↵
     };↵

auto bs = [&](int l, int r, const vector<point> & A, ld w) -> int {↵
        int res = l;↵
        ld w1 = p.x * w;↵
        while(l <= r){↵
int m = (l + r) / 2;↵
if(ge(A[m].x * w, w1)) r = m - 1;↵
else res = m, l = m + 1;↵
}↵
        return res;↵
};↵
    ↵
point left = p, rigth = p;↵

int t1 = bs(0, m - 1, Ps[0], 1), t2 = bs(0, k - 1, Ps[1], -1);↵

auto u1 = tang(0, t1, -1, 0), u2 = tang(0, t2, -1, lk);↵
auto v1 = tang(t1, m - 1, 1, 0), v2 = tang(t2, k - 1, 1, lk);↵

if(leq((P[u1] - p).cross(P[(u1 - 1 + n) % n] - p), 0) && leq((P[u1] - p).cross(P[(u1 + 1) % n] - p), 0)) left = P[u1];↵
if(leq((P[(lk + u2) % n] - p).cross(P[(lk + u2 - 1 + n) % n] - p), 0) && leq((P[(lk + u2) % n] - p).cross(P[(lk + u2 + 1) % n] - p), 0)) left = P[(lk + u2) % n];↵

if(geq((P[v1] - p).cross(P[(v1 - 1 + n) % n] - p), 0) && geq((P[v1] - p).cross(P[(v1 + 1) % n] - p), 0)) rigth = P[v1];↵
if(geq((P[(lk + v2) % n] - p).cross(P[(lk - 1 + n + v2) % n] - p), 0) && geq((P[(lk + v2) % n] - p).cross(P[(lk + 1 + v2) % n] - p), 0)) rigth = P[(lk + v2) % n];↵
    ↵
return {left, rigth};↵
}↵
```cpp↵
</spoiler>↵

Musical recommendation: Visita, Enjambre ^^

History

 
 
 
 
Revisions
 
 
  Rev. Lang. By When Δ Comment
en26 English Jose_17 2025-03-03 22:48:56 0 (published)
en25 English Jose_17 2025-03-03 22:47:32 227 Tiny change: 'ambre ^^\nhttps://' -> 'ambre ^^\n\nhttps://'
en24 English Jose_17 2025-03-03 22:24:09 48 Marckess
en23 English Jose_17 2025-03-03 21:25:44 0 Marckess
en22 English Jose_17 2025-03-03 21:23:48 3 Tiny change: ', Enjambre' -> ', Enjambre ^^'
en21 English Jose_17 2025-03-03 11:04:58 160
en20 English Jose_17 2025-03-03 10:57:53 57 Tiny change: '/details> ```cpp\n\n' -> '/details> \n```cpp\n\n'
en19 English Jose_17 2025-03-03 10:43:08 13 Tiny change: '">\n<br>\n#include' -> '">\n<br>\n```cpp\n#include'
en18 English Jose_17 2025-03-03 10:39:45 6 Tiny change: 'tation">\n\n\n#include' -> 'tation">\n<br>\n#include'
en17 English Jose_17 2025-03-03 10:36:15 52
en16 English Jose_17 2025-03-03 10:32:55 254
en15 English Jose_17 2025-03-03 10:29:49 102
en14 English Jose_17 2025-03-03 10:28:20 344
en13 English Jose_17 2025-03-03 10:25:19 2 Tiny change: 'tation">\nvector<p' -> 'tation">\n\nvector<p'
en12 English Jose_17 2025-03-03 10:23:55 10
en11 English Jose_17 2025-03-03 10:23:37 2365
en10 English Jose_17 2025-03-03 08:09:19 179
en9 English Jose_17 2025-03-03 08:03:12 817
en8 English Jose_17 2025-03-01 04:25:17 60
en7 English Jose_17 2025-03-01 04:23:19 4 Tiny change: 'le="width:350px; height:300px;" />\n' -> 'le="width:500px; height:350px;" />\n'
en6 English Jose_17 2025-03-01 04:22:53 196 Tiny change: '\n\n\n$$\n(P_{i} -' -> '\n\n\n$$\n\newline \n(P_{i} -'
en5 English Jose_17 2025-03-01 03:59:13 2 Tiny change: ' zero:\n\n$$\n(P' -> ' zero:\n\n\n$$\n(P'
en4 English Jose_17 2025-03-01 03:58:35 302
en3 English Jose_17 2025-03-01 03:55:45 469
en2 English Jose_17 2025-02-27 22:35:03 153 Tiny change: 'le="width:500px; heigh' -> 'le="width:350px; heigh'
en1 English Jose_17 2025-02-27 22:27:51 820 Initial revision (saved to drafts)