diff --git a/content/geometry/ConvexHull.h b/content/geometry/ConvexHull.h index 170e27ac5..a9a61dd82 100644 --- a/content/geometry/ConvexHull.h +++ b/content/geometry/ConvexHull.h @@ -20,7 +20,7 @@ Points on the edge of the hull between two other points are not considered part #include "Point.h" -typedef Point P; +template vector

convexHull(vector

pts) { if (SZ(pts) <= 1) return pts; sort(ALL(pts)); diff --git a/content/geometry/HalfplaneIntersection.h b/content/geometry/HalfplaneIntersection.h new file mode 100644 index 000000000..4e1c4eed5 --- /dev/null +++ b/content/geometry/HalfplaneIntersection.h @@ -0,0 +1,53 @@ +/** + * Author: Iván Renison + * Date: 2024-09-04 + * License: CC0 + * Source: notebook el vasito + * Description: Computes the intersection of a set of half-planes. + * Input is given as a set of planes, facing left. + * The intersection must form a convex polygon or be empty. + * Output is the convex polygon representing the intersection in CCW order. + * The points may have duplicates and be collinear. + * Time: O(n \log n) + * Status: stress-tested ans problem tested + */ +#pragma once + +#include "Point.h" +#include "sideOf.h" +#include "lineIntersection.h" + +typedef Point P; +struct Line { + P p, q; + double a; + Line() {} + Line(P p, P q) : p(p), q(q), a((q - p).angle()) {} + bool operator<(Line o) const { return a < o.a; } +}; +#define L(a) a.p, a.q +#define PQ(a) (a.q - a.p) + +vector

halfPlaneIntersection(vector v) { + sort(ALL(v)); + ll n = SZ(v), q = 1, h = 0; + const double eps = 1e-9; + vector c(n+2); + #define I(j, k) lineInter(L(c[j]), L(c[k])).snd + fore(i, 0, n) { + while (q < h && sideOf(L(v[i]), I(h, h-1), eps) < 0) h--; + while (q < h && sideOf(L(v[i]), I(q, q+1), eps) < 0) q++; + c[++h] = v[i]; + if (q < h && abs(PQ(c[h]).cross(PQ(c[h-1]))) < eps) { + if (PQ(c[h]).dot(PQ(c[h-1])) <= 0) return {}; + if (sideOf(L(v[i]), c[--h].p, eps) < 0) c[h] = v[i]; + } + } + while (q < h - 1 && sideOf(L(c[q]), I(h, h-1), eps) < 0) h--; + while (q < h - 1 && sideOf(L(c[h]), I(q, q+1), eps) < 0) q++; + if (h - q <= 1) return {}; + c[++h] = c[q]; + vector

s; + fore(i, q, h) s.pb(I(i, i+1)); + return s; +} diff --git a/content/geometry/chapter.tex b/content/geometry/chapter.tex index 1e4dcd65c..fa50541cd 100644 --- a/content/geometry/chapter.tex +++ b/content/geometry/chapter.tex @@ -8,6 +8,7 @@ \section{Geometric primitives} \kactlimport{lineIntersection.h} \kactlimport{sideOf.h} \kactlimport{OnSegment.h} + \kactlimport{HalfplaneIntersection.h} \kactlimport{linearTransformation.h} \kactlimport{LineProjectionReflection.h} \kactlimport{Angle.h} diff --git a/stress-tests/geometry/ConvexHull.cpp b/stress-tests/geometry/ConvexHull.cpp index c92669f02..f3cb62bed 100644 --- a/stress-tests/geometry/ConvexHull.cpp +++ b/stress-tests/geometry/ConvexHull.cpp @@ -3,6 +3,8 @@ #include "../../content/geometry/ConvexHull.h" #include "../utilities/bench.h" +typedef Point P; + namespace old { pair ulHull(const vector

& S) { vi Q(SZ(S)), U, L; diff --git a/stress-tests/geometry/FastDelaunay.cpp b/stress-tests/geometry/FastDelaunay.cpp index 0d171fe16..51d6c8dea 100644 --- a/stress-tests/geometry/FastDelaunay.cpp +++ b/stress-tests/geometry/FastDelaunay.cpp @@ -9,6 +9,8 @@ #include "../../content/geometry/circumcircle.h" #undef P +typedef Point P; + P2 top(P x) { return P2((double)x.x, (double)x.y); } struct Bumpalloc { diff --git a/stress-tests/geometry/HalfplaneIntersection.cpp b/stress-tests/geometry/HalfplaneIntersection.cpp new file mode 100644 index 000000000..bc17f9c4c --- /dev/null +++ b/stress-tests/geometry/HalfplaneIntersection.cpp @@ -0,0 +1,186 @@ +#include "../utilities/template.h" +#include "./utilities.h" + +#include "../../content/geometry/HalfplaneIntersection.h" +#include "../../content/geometry/ConvexHull.h" + +#pragma GCC optimize ("trapv") + +// Test against brote force with fractions +namespace slow { + + typedef __int128_t i128; + + struct frac { + i128 num, den; + frac(i128 num_ = 0, i128 den_ = 1) : num(num_), den(den_) { + i128 g = __gcd(num, den); + num /= g; + den /= g; + if (den < 0) { + num = -num; + den = -den; + } + assert(den > 0); + } + bool operator<(frac f) const { + return num * f.den < f.num * den; + } + bool operator==(frac f) const { + return num == f.num && den == f.den; + } + bool operator<=(frac f) const { + return num * f.den <= f.num * den; + } + bool operator>(frac f) const { + return num * f.den > f.num * den; + } + frac operator+(frac o) const { + return frac(num * o.den + o.num * den, den * o.den); + } + frac operator-(frac o) const { + return frac(num * o.den - o.num * den, den * o.den); + } + frac operator*(frac o) const { + return frac(num * o.num, den * o.den); + } + frac operator/(frac o) const { + return frac(num * o.den, den * o.num); + } + }; + + typedef Point Pf; + + vector slow(const vector>& t) { + ll n = SZ(t); + vector points; + fore(i, 0, n) fore(j, 0, i) { + auto [si, ei] = t[i]; + auto [sj, ej] = t[j]; + auto [x, p] = lineInter(si, ei, sj, ej); + if (x == 1) { + points.push_back(p); + } + } + + vector ans; + for (Pf p : points) { + bool valid = true; + for (auto [s, e] : t) { + ll side = sideOf(s, e, p); + if (side == -1) { + valid = false; + break; + } + } + if (valid) { + ans.push_back(p); + } + } + + ans = convexHull(ans); + return ans; + } +} + +typedef slow::Pf Pf; + +P Pf_to_P(Pf p) { + return P((double)p.x.num / p.x.den, (double)p.y.num / p.y.den); +} + +Pf randIntPt(ll lim) { + return Pf{rand() % (2 * lim + 1) - lim, rand() % (2 * lim + 1) - lim}; +} + +slow::frac randFrac(ll lim) { + ll den = rand() % lim + 1; + ll num = rand() % den; + return slow::frac(num, den); +} + +Pf randDoublePt(ll lim) { + Pf ans = randIntPt(lim); + ans.x = ans.x + randFrac(lim / 2); + ans.y = ans.y + randFrac(lim / 2); + return ans; +} + + +const slow::frac INF = 500; +void addInf(vector> &ans, slow::frac INF = INF) { + slow::frac nINF = slow::frac() - INF; + vector infPts({Pf(INF, INF), Pf(nINF, INF), Pf(nINF, nINF), Pf(INF, nINF)}); + fore(i, 0, 4) { + ans.push_back({infPts[i], infPts[(i + 1) % 4]}); + } +} + +void test(const vector>& t) { + const double eps = 1e-11; + vector t_(SZ(t)); + fore(i, 0, SZ(t)) { + t_[i] = {Pf_to_P(t[i].first), Pf_to_P(t[i].second)}; + } + vector

ans = halfPlaneIntersection(t_); + assert(isConvexCCW(ans, eps)); + ans = convexHull(ans); // Remove colinear + vector ansf = slow::slow(t); + vector

ans2(SZ(ansf)); + fore(i, 0, SZ(ansf)) { + ans2[i] = Pf_to_P(ansf[i]); + } + assert(polygonEq(ans, ans2, eps)); +} + +void testRandomInt() { + ll n = rand() % 10 + 1; + vector> t; + fore(i, 0, n) { + Pf p = randIntPt(10); + Pf q = randIntPt(10); + if (p == q) continue; + t.push_back({p, q}); + } + addInf(t); + test(t); +} + +void testRandomDouble() { + ll n = rand() % 10 + 1; + vector> t; + fore(i, 0, n) { + Pf p = randDoublePt(10); + Pf q = randDoublePt(10); + if (p == q) continue; + t.push_back({p, q}); + } + addInf(t); + test(t); +} + +int main() { + + vector>> handmade = { + {{Pf(0, 0), Pf(5, 0)}, {Pf(5, -2), Pf(5, 2)}, {Pf(5, 2), Pf(2, 2)}, {Pf(0, 3), Pf(0, -3)}}, + {{Pf(0, 0), Pf(5, 0)}}, + {{Pf(0, 0), Pf(5, 0)}, {Pf(5, 0), Pf(0, 0)}}, // Line + {{Pf(0, 0), Pf(5, 0)}, {Pf(5, 0), Pf(0, 0)}, {Pf(0, 0), Pf(0, 5)}, {Pf(0, 5), Pf(0, 0)}}, // Point + {{Pf(0, 0), Pf(5, 0)}, {Pf(5, 0), Pf(0, 0)}, {Pf(0, 0), Pf(0, 5)}, {Pf(0, 5), Pf(0, 0)}, {Pf(0, 2), Pf(5, 2)}}, // Empty + {{Pf(0, 0), Pf(5, 0)}, {Pf(5, 0), Pf(5, 5)}, {Pf(5, 5), Pf(0, 5)}, {Pf(0, 5), Pf(0, 0)}, {Pf(1, 5), Pf(1, 0)}}, // Parallel lines + {{Pf(0, 0), Pf(5, 0)}, {Pf(5, 0), Pf(5, 5)}, {Pf(5, 5), Pf(0, 5)}, {Pf(1, 5), Pf(1, 0)}, {Pf(0, 5), Pf(0, 0)}}, // Parallel lines + {{Pf(0, 0), Pf(1, 0)}, {Pf(0, 0), Pf(2, 0)}, {Pf(0, 0), Pf(3, 0)}} + }; + + for (auto& t : handmade) { + addInf(t); + test(t); + } + + fore(_, 0, 1000) { + testRandomInt(); + testRandomDouble(); + } + + cout << "Tests passed!" << endl; +} diff --git a/stress-tests/geometry/HalfplaneIntersection2.cpp b/stress-tests/geometry/HalfplaneIntersection2.cpp new file mode 100644 index 000000000..ef5dfa7fb --- /dev/null +++ b/stress-tests/geometry/HalfplaneIntersection2.cpp @@ -0,0 +1,121 @@ +#include "../utilities/template.h" +#include "../utilities/randGeo.h" +#include "./utilities.h" + +#include "../../content/geometry/HalfplaneIntersection.h" +#include "../../content/geometry/ConvexHull.h" + +const double eps = 1e-11; + +// Test against brote force with float128 +typedef Point<__float128> Pf; +vector slow(const vector>& t) { + + ll n = SZ(t); + vector points; + fore(i, 0, n) fore(j, 0, i) { + auto [si, ei] = t[i]; + auto [sj, ej] = t[j]; + auto [x, p] = lineInter(si, ei, sj, ej); + if (x == 1) { + points.push_back(p); + } + } + + vector ans; + for (Pf p : points) { + bool valid = true; + for (auto [s, e] : t) { + ll side = sideOf(s, e, p, eps); + if (side == -1) { + valid = false; + break; + } + } + if (valid) { + ans.push_back(p); + } + } + + ans = convexHull(ans); + return ans; +} + +const double INF = 1000; +void addInf(vector &ans, double INF = INF) { + vector

infPts({P(INF, INF), P(-INF, INF), P(-INF, -INF), P(INF, -INF)}); + fore(i, 0, 4) { + ans.push_back({infPts[i], infPts[(i + 1) % 4]}); + } +} + +void test(const vector& t) { + vector

ans = halfPlaneIntersection(t); + assert(isConvexCCW(ans, eps)); + ans = convexHull(ans); // Remove colinear + vector> t2(SZ(t)); + fore(i, 0, SZ(t)) { + auto [p, q, _] = t[i]; + auto [px, py] = p; + auto [qx, qy] = q; + t2[i] = {Pf(px, py), Pf(qx, qy)}; + } + vector ans2_ = slow(t2); + vector

ans2(SZ(ans2_)); + fore(i, 0, SZ(ans2_)) { + auto [x, y] = ans2_[i]; + ans2[i] = P((double)x, (double)y); + } + assert(polygonEq(ans, ans2, eps)); +} + +void testRandomInt() { + ll n = rand() % 100 + 1; + vector t; + fore(i, 0, n) { + P p = randIntPt(100); + P q = randIntPt(100); + if (p == q) continue; + t.push_back({p, q}); + } + addInf(t); + test(t); +} + +void testRandomDouble() { + ll n = rand() % 100 + 1; + vector t; + fore(i, 0, n) { + P p = randDoublePt(100); + P q = randDoublePt(100); + if ((p - q).dist2() < eps) continue; + t.push_back({p, q}); + } + addInf(t); + test(t); +} + +int main() { + + vector> handmade = { + {{P(0, 0), P(5, 0)}, {P(5, -2), P(5, 2)}, {P(5, 2), P(2, 2)}, {P(0, 3), P(0, -3)}}, + {{P(0, 0), P(5, 0)}}, + {{P(0, 0), P(5, 0)}, {P(5, 0), P(0, 0)}}, // Line + {{P(0, 0), P(5, 0)}, {P(5, 0), P(0, 0)}, {P(0, 0), P(0, 5)}, {P(0, 5), P(0, 0)}}, // Point + {{P(0, 0), P(5, 0)}, {P(5, 0), P(0, 0)}, {P(0, 0), P(0, 5)}, {P(0, 5), P(0, 0)}, {P(0, 2), P(5, 2)}}, // Empty + {{P(0, 0), P(5, 0)}, {P(5, 0), P(5, 5)}, {P(5, 5), P(0, 5)}, {P(0, 5), P(0, 0)}, {P(1, 5), P(1, 0)}}, // Parallel lines + {{P(0, 0), P(5, 0)}, {P(5, 0), P(5, 5)}, {P(5, 5), P(0, 5)}, {P(1, 5), P(1, 0)}, {P(0, 5), P(0, 0)}} // Parallel lines + }; + + for (auto& t : handmade) { + addInf(t); + test(t); + } + + fore(_, 0, 1000) { + testRandomInt(); + testRandomDouble(); + } + + cout << "Tests passed!" << endl; +} diff --git a/stress-tests/geometry/LineHullIntersection.cpp b/stress-tests/geometry/LineHullIntersection.cpp index dc999f809..577d9f43e 100644 --- a/stress-tests/geometry/LineHullIntersection.cpp +++ b/stress-tests/geometry/LineHullIntersection.cpp @@ -32,7 +32,9 @@ struct Point { return P(x*cos(a)-y*sin(a),x*sin(a)+y*cos(a)); } }; + #include "../../content/geometry/ConvexHull.h" +typedef Point P; #include "../../content/geometry/LineHullIntersection.h" ll segmentIntersection(const P& s1, const P& e1, diff --git a/stress-tests/geometry/utilities.h b/stress-tests/geometry/utilities.h index c960fd5be..97d5c1f75 100644 --- a/stress-tests/geometry/utilities.h +++ b/stress-tests/geometry/utilities.h @@ -45,8 +45,8 @@ template bool polygonEq(vector

& p, vector

& q, double eps) { ll n = SZ(p); if (n != SZ(q)) return false; - if (p == q) return true; - fore(i, 1, n) { + if (n == 0) return true; + fore(i, 0, n) { bool valid = true; fore(j, 0, n) { if (abs(p[j].x - q[(j + i) % n].x) > eps || abs(p[j].y - q[(j + i) % n].y) > eps) { diff --git a/stress-tests/utilities/randGeo.h b/stress-tests/utilities/randGeo.h index dbeee56fc..dfb0b2850 100644 --- a/stress-tests/utilities/randGeo.h +++ b/stress-tests/utilities/randGeo.h @@ -6,3 +6,10 @@ template Point randIntPt(ll lim) { return Point(rand()%(lim*2) - lim, rand()%(lim*2)-lim); } + +template +Point randDoublePt(ll lim) { + static default_random_engine re; + uniform_real_distribution unif(-lim, lim); + return Point(unif(re), unif(re)); +} diff --git a/test-problems/geometry/HalfplaneIntersection.cpp b/test-problems/geometry/HalfplaneIntersection.cpp new file mode 100644 index 000000000..619aa8b96 --- /dev/null +++ b/test-problems/geometry/HalfplaneIntersection.cpp @@ -0,0 +1,189 @@ +// Problem: https://codeforces.com/gym/104736/problem/H +// Status: Accepted +#include +using namespace std; + +#define fst first +#define snd second +#define pb push_back +#define fore(i, a, b) for (ll i = a, gmat = b; i < gmat; i++) +#define ALL(x) begin(x), end(x) +#define SZ(x) (ll)(x).size() +#define mset(a, v) memset((a), (v), sizeof(a)) +typedef long long ll; +typedef pair ii; +typedef vector vi; + +/// content/geometry/Point.h +template ll sgn(T x) { return (x > 0) - (x < 0); } +template +struct Point { + typedef Point P; + T x, y; + explicit Point(T x=0, T y=0) : x(x), y(y) {} + bool operator<(P p) const { return tie(x,y) < tie(p.x,p.y); } + bool operator==(P p) const { return tie(x,y)==tie(p.x,p.y); } + P operator+(P p) const { return P(x+p.x, y+p.y); } + P operator-(P p) const { return P(x-p.x, y-p.y); } + P operator*(T d) const { return P(x*d, y*d); } + P operator/(T d) const { return P(x/d, y/d); } + T dot(P p) const { return x*p.x + y*p.y; } + T cross(P p) const { return x*p.y - y*p.x; } + T cross(P a, P b) const { return (a-*this).cross(b-*this); } + T dist2() const { return x*x + y*y; } + double dist() const { return sqrt((double)dist2()); } + // angle to x-axis in interval [-pi, pi] + double angle() const { return atan2(y, x); } + P unit() const { return *this/dist(); } // makes dist()=1 + P perp() const { return P(-y, x); } // rotates +90 degrees + P normal() const { return perp().unit(); } + // returns point rotated 'a' radians ccw around the origin + P rotate(double a) const { + return P(x*cos(a)-y*sin(a),x*sin(a)+y*cos(a)); } + friend ostream& operator<<(ostream& os, P p) { + return os << "(" << p.x << "," << p.y << ")"; } +}; +/// END content + +/// content/geometry/sideOf.h +template +ll sideOf(P s, P e, P p) { return sgn(s.cross(e, p)); } + +template +ll sideOf(const P& s, const P& e, const P& p, double eps) { + auto a = (e-s).cross(p-s); + double l = (e-s).dist()*eps; + return (a > l) - (a < -l); +} +/// END content + +/// content/geometry/lineIntersection.h +template +pair lineInter(P s1, P e1, P s2, P e2) { + auto d = (e1 - s1).cross(e2 - s2); + if (d == 0) // if parallel + return {-(s1.cross(e1, s2) == 0), P(0, 0)}; + auto p = s2.cross(e1, e2), q = s2.cross(e2, s1); + return {1, (s1 * p + e1 * q) / d}; +} +/// END content + +/// content/geometry/HalfplaneIntersection.h +typedef Point P; +struct Line { + P p, q; + double a; + Line() {} + Line(P p, P q) : p(p), q(q), a((q - p).angle()) {} + bool operator<(Line o) const { return a < o.a; } +}; +#define L(a) a.p, a.q +#define PQ(a) (a.q - a.p) + +vector

halfPlaneIntersection(vector v) { + sort(ALL(v)); + ll n = SZ(v), q = 1, h = 0; + const double eps = 1e-9; + vector c(n+2); + #define I(j, k) lineInter(L(c[j]), L(c[k])).snd + fore(i, 0, n) { + while (q < h && sideOf(L(v[i]), I(h, h-1), eps) < 0) h--; + while (q < h && sideOf(L(v[i]), I(q, q+1), eps) < 0) q++; + c[++h] = v[i]; + if (q < h && abs(PQ(c[h]).cross(PQ(c[h-1]))) < eps) { + if (PQ(c[h]).dot(PQ(c[h-1])) <= 0) return {}; + if (sideOf(L(v[i]), c[--h].p, eps) < 0) c[h] = v[i]; + } + } + while (q < h - 1 && sideOf(L(c[q]), I(h, h-1), eps) < 0) h--; + while (q < h - 1 && sideOf(L(c[h]), I(q, q+1), eps) < 0) q++; + if (h - q <= 1) return {}; + c[++h] = c[q]; + vector

s; + fore(i, q, h) s.pb(I(i, i+1)); + return s; +} +/// END content + +const ll inf = 1ll << 60; +const double dinf = 1e15; +const double eps = 1e-9; + +double max_dist2(vector

& points) { + double ans = 0; + for (P p : points) { + ans = max(ans, p.dist2()); + } + return ans; +} + +ll solve(double D, vector& lines) { + ll N = SZ(lines); + + for (auto& l : lines) { + auto [p0, p1, _] = l; + if (sideOf(p0, p1, P(0, 0), eps) == -1) { + l = {p1, p0}; + } + } + + vector big_box = { + {P(dinf, dinf), P(-dinf, dinf)}, + {P(-dinf, dinf), P(-dinf, -dinf)}, + {P(-dinf, -dinf), P(dinf, -dinf)}, + {P(dinf, -dinf), P(dinf, dinf)}, + }; + + double D2 = D * D; + + + { // Test total + lines.insert(lines.end(), ALL(big_box)); + + vector

poly = halfPlaneIntersection(lines); + double poly_dist2 = max_dist2(poly); + if (poly_dist2 > D2) { + return inf; + } + } + + ll l = 1, u = N; + while (l + 1 < u) { + ll m = (l + u) / 2; + + vector this_lines(lines.begin(), lines.begin() + m); + this_lines.insert(this_lines.end(), ALL(big_box)); + + vector

poly = halfPlaneIntersection(this_lines); + double poly_dist2 = max_dist2(poly); + if (poly_dist2 < D2) { + u = m; + } else { + l = m; + } + } + + return u; +} + +int main() { + cin.tie(0)->sync_with_stdio(0); + + ll N; + double D; + cin >> N >> D; + vector lines(N); + for (auto& l : lines) { + ll X0, Y0, X1, Y1; + cin >> X0 >> Y0 >> X1 >> Y1; + l = {P(X0, Y0), P(X1, Y1)}; + } + + ll ans = solve(D, lines); + if (ans == inf) { + cout << '*'; + } else { + cout << ans; + } + cout << '\n'; +} diff --git a/test-problems/geometry/HalfplaneIntersection2.cpp b/test-problems/geometry/HalfplaneIntersection2.cpp new file mode 100644 index 000000000..7349506b2 --- /dev/null +++ b/test-problems/geometry/HalfplaneIntersection2.cpp @@ -0,0 +1,163 @@ +// Problem: https://codeforces.com/gym/101309/problem/J +// Status: Accepted +#include +using namespace std; + +#define fst first +#define snd second +#define pb push_back +#define fore(i, a, b) for (ll i = a, gmat = b; i < gmat; i++) +#define ALL(x) begin(x), end(x) +#define SZ(x) (ll)(x).size() +#define mset(a, v) memset((a), (v), sizeof(a)) +typedef long long ll; +typedef pair ii; +typedef vector vi; + +/// content/geometry/Point.h +template ll sgn(T x) { return (x > 0) - (x < 0); } +template +struct Point { + typedef Point P; + T x, y; + explicit Point(T x=0, T y=0) : x(x), y(y) {} + bool operator<(P p) const { return tie(x,y) < tie(p.x,p.y); } + bool operator==(P p) const { return tie(x,y)==tie(p.x,p.y); } + P operator+(P p) const { return P(x+p.x, y+p.y); } + P operator-(P p) const { return P(x-p.x, y-p.y); } + P operator*(T d) const { return P(x*d, y*d); } + P operator/(T d) const { return P(x/d, y/d); } + T dot(P p) const { return x*p.x + y*p.y; } + T cross(P p) const { return x*p.y - y*p.x; } + T cross(P a, P b) const { return (a-*this).cross(b-*this); } + T dist2() const { return x*x + y*y; } + double dist() const { return sqrt((double)dist2()); } + // angle to x-axis in interval [-pi, pi] + double angle() const { return atan2(y, x); } + P unit() const { return *this/dist(); } // makes dist()=1 + P perp() const { return P(-y, x); } // rotates +90 degrees + P normal() const { return perp().unit(); } + // returns point rotated 'a' radians ccw around the origin + P rotate(double a) const { + return P(x*cos(a)-y*sin(a),x*sin(a)+y*cos(a)); } + friend ostream& operator<<(ostream& os, P p) { + return os << "(" << p.x << "," << p.y << ")"; } +}; +/// END content + +/// content/geometry/sideOf.h +template +ll sideOf(P s, P e, P p) { return sgn(s.cross(e, p)); } + +template +ll sideOf(const P& s, const P& e, const P& p, double eps) { + auto a = (e-s).cross(p-s); + double l = (e-s).dist()*eps; + return (a > l) - (a < -l); +} +/// END content + +/// content/geometry/lineIntersection.h +template +pair lineInter(P s1, P e1, P s2, P e2) { + auto d = (e1 - s1).cross(e2 - s2); + if (d == 0) // if parallel + return {-(s1.cross(e1, s2) == 0), P(0, 0)}; + auto p = s2.cross(e1, e2), q = s2.cross(e2, s1); + return {1, (s1 * p + e1 * q) / d}; +} +/// END content + +/// content/geometry/HalfplaneIntersection.h +typedef Point P; +struct Line { + P p, q; + double a; + Line() {} + Line(P p, P q) : p(p), q(q), a((q - p).angle()) {} + bool operator<(Line o) const { return a < o.a; } +}; +#define L(a) a.p, a.q +#define PQ(a) (a.q - a.p) + +vector

halfPlaneIntersection(vector v) { + sort(ALL(v)); + ll n = SZ(v), q = 1, h = 0; + const double eps = 1e-9; + vector c(n+2); + #define I(j, k) lineInter(L(c[j]), L(c[k])).snd + fore(i, 0, n) { + while (q < h && sideOf(L(v[i]), I(h, h-1), eps) < 0) h--; + while (q < h && sideOf(L(v[i]), I(q, q+1), eps) < 0) q++; + c[++h] = v[i]; + if (q < h && abs(PQ(c[h]).cross(PQ(c[h-1]))) < eps) { + if (PQ(c[h]).dot(PQ(c[h-1])) <= 0) return {}; + if (sideOf(L(v[i]), c[--h].p, eps) < 0) c[h] = v[i]; + } + } + while (q < h - 1 && sideOf(L(c[q]), I(h, h-1), eps) < 0) h--; + while (q < h - 1 && sideOf(L(c[h]), I(q, q+1), eps) < 0) q++; + if (h - q <= 1) return {}; + c[++h] = c[q]; + vector

s; + fore(i, q, h) s.pb(I(i, i+1)); + return s; +} +/// END content + +/// content/geometry/PolygonArea.h +template +T polygonArea2(vector>& v) { + T a = v.back().cross(v[0]); + fore(i,0,SZ(v)-1) a += v[i].cross(v[i+1]); + return a; +} +/// END content + +const double eps = 1e-11; + +ll solve(vector

& points) { + ll n = SZ(points); + + ll l = 0, r = n - 2; + while (l + 1 < r) { + ll m = (l + r) / 2; + + vector lines(n); + fore(i, 0, n) { + lines[i] = {points[(i + m + 1) % n], points[i]}; + } + + vector

poly = halfPlaneIntersection(lines); + + double area = poly.size() <= 2 ? 0 : polygonArea2(poly); + + if (area <= eps) { + r = m; + } else { + l = m; + } + } + + return r; +} + +int main() { + cin.tie(0)->sync_with_stdio(0); +#ifdef ONLINE_JUDGE + freopen("jungle.in","r",stdin); + freopen("jungle.out","w",stdout); +#endif + + ll n; + cin >> n; + vector

points(n); + for (auto& [x, y] : points) { + ll X, Y; + cin >> X >> Y; + x = X, y = Y; + } + + auto ans = solve(points); + cout << ans << '\n'; +}