Half-Plane Intersection
Key Points
- •Half-plane intersection (HPI) computes the common region that satisfies many linear side-of-line constraints in the plane.
- •The standard O(n n) algorithm sorts half-planes by angle and maintains a deque of active constraints, pruning ends when they are cut off by the new constraint.
- •Represent each half-plane as an oriented line with its feasible side on the left; a point is inside if cross(v, x − p) ≥ 0.
- •Handle parallel half-planes by keeping the most restrictive one (largest offset along the left normal) and use an EPS to fight floating-point noise.
- •The intersection, if non-empty and bounded, is a convex polygon; if empty or unbounded, detect and report accordingly.
- •HPI underlies 2D linear programming, convex polygon clipping, and Voronoi cell computation within a bounding box.
- •Numerical robustness is crucial: normalize directions, compare angles with tolerance, and re-check endpoints after inserting all half-planes.
- •The resulting polygon has at most n vertices, and evaluating linear objectives on it reduces to checking its vertices.
Prerequisites
- →2D vectors and operations — HPI relies on dot and cross products, norms, and orientation tests.
- →Sorting and custom comparators — Half-planes must be sorted by angle with careful tie-breaking for parallels.
- →Deque data structure — The algorithm maintains a double-ended queue of active constraints to prune ends efficiently.
- →Lines and intersections — Computing intersection points between lines is central to updating polygon corners.
- →Floating-point arithmetic and EPS — Robustness requires tolerances, normalization, and guarding against near-parallel cases.
- →Convex sets and polygons — Understanding that intersections of half-planes are convex underpins the deque trimming logic.
- →Linear programming basics (2D) — HPI directly solves 2D LP and motivates the need for half-plane intersections.
Detailed Explanation
Tap terms for definitions01Overview
Imagine many straight lines drawn on the plane, and for each line you choose one side as allowed (for example, “above this line” or “to the left of this line”). The half-plane intersection (HPI) problem asks: which points satisfy all these side-of-line constraints at once? The answer is either empty (no common point), unbounded (extends to infinity), or a bounded convex polygon. The classical algorithm solves HPI in O(n \log n) time by sorting constraints by angle and maintaining a double-ended queue (deque) of the “currently active” half-planes that still matter. When a new half-plane arrives, you remove from the back (or front) any planes whose pairwise intersection points lie outside the new constraint, thus trimming the feasible region from ends. This method exploits convexity: every cut keeps the feasible set convex, and each half-plane can remove only a contiguous portion from the current boundary. HPI is widely used in 2D linear programming (maximizing a linear function under linear inequalities), polygon clipping (e.g., Sutherland–Hodgman generalization), and computational geometry tasks like computing a site’s Voronoi cell within a bounding box. Implementations require careful numeric handling (EPS tolerances, parallel lines, and final cleanup).
02Intuition & Analogies
Think of each half-plane as a huge sheet of paper that covers the allowed side of its defining line. If you stack all these sheets together, the overlap is the set of points that every sheet covers—that’s the feasible region. Another analogy is slicing a block of clay with knives: every knife makes a straight cut and you keep only the clay on one chosen side. After all cuts, you either have no clay (empty), a slab that stretches infinitely (unbounded), or a neat convex polyhedron in 2D—a convex polygon. The algorithm’s deque acts like a smart way to keep track of the “outer rim” of the remaining clay. When a new cut arrives, only the two ends (front and back) of this rim can become invalid first, because the region stays convex and only the newest constraint can invalidate corners that stick out too far on either end. Sorting by angle is like organizing cuts by their directions so that parallel or near-parallel cuts are processed consistently. Handling parallel cuts is like deciding, among two knives with the same direction, which one actually matters: the one that bites deeper into the clay (more restrictive) determines the final surface. EPS tolerances are the safety gloves: without them, tiny numerical wiggles can make you misjudge whether a corner is inside or outside, leading to wrong conclusions about emptiness or polygon shape.
03Formal Definition
04When to Use
Use HPI when you need the exact feasible region of many linear inequalities in 2D and can tolerate floating-point computation: (1) 2D linear programming—maximize or minimize a linear functional (\mathbf{c}\cdot\mathbf{x}) subject to (A\mathbf{x} \le \mathbf{b}). The optimum, if bounded, lies on a vertex of the HPI polygon. (2) Voronoi cells within a bounding box—each competitor site contributes a half-plane defined by a perpendicular bisector; intersecting them yields the cell polygon. (3) Convex polygon clipping—intersect an initial convex polygon (or large bounding box) with a set of half-planes to model visibility, shadow volumes, or collision regions. (4) Feasibility checks—quickly detect emptiness of constraints in 2D. (5) Robust geometric preprocessing—trim search spaces for further algorithms (e.g., rotating calipers, support mapping) by enforcing linear bounds. Do not use HPI if constraints are non-linear, in high-dimensional spaces (2D-specific algorithm), or if exact rational arithmetic is required (floating-point HPI needs careful tolerance). For 3D and higher, consider half-space intersection using different data structures or linear programming solvers.
⚠️Common Mistakes
- Inconsistent orientation: mixing “left-of-line” and “right-of-line” conventions causes silent errors. Pick one convention (e.g., inside if cross(v, x−p) ≥ 0) and stick to it for all half-planes. - Mishandling parallel lines: if two lines have the same angle, keep only the most restrictive half-plane (largest offset along the left normal). Keeping both can make the deque logic fail or lead to incorrect unbounded/empty detection. - No EPS or too-tight EPS: floating-point comparisons like cross(...) ≥ 0 must use a tolerance (e.g., 1e−12 with long double). Too small EPS fails under noise; too large EPS distorts geometry. - Forgetting final cleanup: after processing all half-planes, you must re-check the ends of the deque against the first/last constraints; otherwise, stale corners might remain. - Wrong intersection formula or not guarding near-parallel denominators: always check |cross(v1, v2)| against EPS. - Assuming boundedness: the intersection can be unbounded; if you need a polygon, first clip with a big bounding rectangle (or add constraints) to bound it. - Angle tie-breaking by atan2 only: equal angles need extra handling; normalizing direction vectors reduces surprises. - Returning degenerate outputs: if fewer than three constraints survive, the intersection is empty or a line/point—treat as empty polygon for many applications.
Key Formulas
2D Cross Product
Explanation: This scalar measures the signed area of the parallelogram spanned by a and b. Its sign tells if b is to the left (positive) or right (negative) of a.
Left Normal
Explanation: Rotating a direction vector v by +90° gives the left normal n. The half-plane left of v satisfies n·.
Line-Line Intersection Parameter
Explanation: For two non-parallel lines, the parameter t locates the intersection on the first line. The denominator being zero indicates parallel lines.
Half-plane Equivalence
Explanation: Using v = (b, −a) and choosing p so that a + b makes the algebraic half-plane equal to the geometric left-of-line condition.
Line Angle
Explanation: The angle of a direction vector is used to sort lines. Equal or nearly equal angles imply parallelism that needs special handling.
Shoelace Area
Explanation: Given the polygon vertices in cyclic order (with ), this computes the polygon’s area. Useful to detect emptiness or degenerate outputs.
HPI Time Complexity
Explanation: Sorting the n half-planes by angle costs O(n log n). The deque phase is linear because each half-plane is inserted once and popped at most once from each end.
HPI Space Complexity
Explanation: We store the sorted half-planes, a deque of active half-planes, and O(n) intersection points, all linear in n.
Voronoi Half-plane
Explanation: Being closer to s than to t can be written as a linear inequality, yielding a half-plane whose boundary is the perpendicular bisector of s and t.
Support Function and LP Optimum
Explanation: Maximizing a linear objective over the convex polygon P equals its support function in direction c and is attained at a vertex of P.
Complexity Analysis
Code Examples
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 struct Point { 5 long double x, y; 6 Point() : x(0), y(0) {} 7 Point(long double _x, long double _y) : x(_x), y(_y) {} 8 Point operator+(const Point& o) const { return Point(x + o.x, y + o.y); } 9 Point operator-(const Point& o) const { return Point(x - o.x, y - o.y); } 10 Point operator*(long double k) const { return Point(x * k, y * k); } 11 }; 12 13 static const long double EPS = 1e-12L; 14 15 long double dot(const Point& a, const Point& b) { return a.x * b.x + a.y * b.y; } 16 long double cross(const Point& a, const Point& b) { return a.x * b.y - a.y * b.x; } 17 long double norm(const Point& a) { return sqrtl(dot(a, a)); } 18 Point rotCCW90(const Point& a) { return Point(-a.y, a.x); } 19 Point rotCW90(const Point& a) { return Point(a.y, -a.x); } 20 21 struct HalfPlane { 22 Point p; // a point on the line 23 Point v; // direction (unit length) 24 long double ang; // atan2(v.y, v.x) 25 HalfPlane() {} 26 HalfPlane(Point _p, Point _v) { 27 long double len = norm(_v); 28 if (len == 0) throw runtime_error("Zero direction in HalfPlane"); 29 v = Point(_v.x / len, _v.y / len); 30 p = _p; 31 ang = atan2l(v.y, v.x); 32 } 33 // Inside check: point x is inside if it is to the left of oriented line (p, v) 34 bool contains(const Point& x) const { 35 return cross(v, x - p) >= -EPS; 36 } 37 // For ax + by <= c, produce left-of-line half-plane with v = rotateCW90(n) 38 static HalfPlane from_le(long double a, long double b, long double c) { 39 Point n(a, b); 40 Point v = rotCW90(n); // so that left normal equals n 41 long double n2 = dot(n, n); 42 if (n2 == 0) throw runtime_error("Invalid inequality normal"); 43 // choose p on the line n·p = c 44 Point p = n * (c / n2); 45 return HalfPlane(p, v); 46 } 47 }; 48 49 // Intersection point of lines of two half-planes (assumed non-parallel) 50 Point line_intersection(const HalfPlane& A, const HalfPlane& B) { 51 // Solve A.p + A.v * t = B.p + B.v * s 52 Point u = B.p - A.p; 53 long double den = cross(B.v, A.v); 54 // Caller should ensure |den| > EPS 55 long double t = cross(B.v, u) / den; 56 return A.p + A.v * t; 57 } 58 59 bool halfplane_cmp(const HalfPlane& a, const HalfPlane& b) { 60 if (fabsl(a.ang - b.ang) > 1e-15L) return a.ang < b.ang; 61 // For equal angles, keep the most restrictive: larger offset along left normal n = rotCCW90(v) 62 Point na = rotCCW90(a.v); 63 long double da = dot(na, a.p); 64 long double db = dot(na, b.p); // same normal because angles equal 65 return da > db; // sort descending so first is most restrictive 66 } 67 68 // Core HPI: returns true with polygon if non-empty and bounded by surviving half-planes; false if empty. 69 bool halfplane_intersection(vector<HalfPlane> hp, vector<Point>& poly) { 70 // 1) Sort by angle; for equal angles, most restrictive first 71 sort(hp.begin(), hp.end(), halfplane_cmp); 72 73 // 2) Remove exact duplicates of angle keeping the first (most restrictive) 74 vector<HalfPlane> filt; filt.reserve(hp.size()); 75 for (size_t i = 0; i < hp.size(); ++i) { 76 if (i == 0 || fabsl(hp[i].ang - hp[i-1].ang) > 1e-15L) filt.push_back(hp[i]); 77 // else skip less restrictive parallels 78 } 79 80 deque<HalfPlane> dq; 81 deque<Point> pts; // intersection points between consecutive half-planes in dq 82 83 auto is_outside = [&](const HalfPlane& H, const Point& X) { 84 return !H.contains(X); 85 }; 86 87 auto push_halfplane = [&](const HalfPlane& H) { 88 // Prune from back while last corner is outside H 89 while (!dq.empty() && !pts.empty() && is_outside(H, pts.back())) { 90 dq.pop_back(); 91 pts.pop_back(); 92 } 93 // Prune from front while first corner is outside H 94 while (!dq.empty() && !pts.empty() && is_outside(H, pts.front())) { 95 dq.pop_front(); 96 pts.pop_front(); 97 } 98 // If last is parallel to H, keep the more restrictive (H is already most restrictive per sort) 99 if (!dq.empty() && fabsl(cross(dq.back().v, H.v)) < EPS) { 100 // Replace last with H (since H is more restrictive due to sorting) 101 dq.pop_back(); 102 if (!pts.empty()) pts.pop_back(); 103 // Also check parallel with front if dq size==1 and equal angle 104 if (!dq.empty() && fabsl(cross(dq.front().v, H.v)) < EPS) { 105 dq.pop_front(); 106 if (!pts.empty()) pts.pop_front(); 107 } 108 } 109 if (!dq.empty()) { 110 // Compute intersection with previous last 111 if (fabsl(cross(dq.back().v, H.v)) < EPS) { 112 // Parallel after clean-up shouldn't happen, but guard 113 if (!H.contains(dq.back().p)) return; // will be emptied later 114 // Otherwise, ignore as duplicate 115 } else { 116 Point ip = line_intersection(dq.back(), H); 117 pts.push_back(ip); 118 } 119 } 120 dq.push_back(H); 121 }; 122 123 for (const auto& H : filt) push_halfplane(H); 124 125 // Final cleanup: ensure ends are consistent 126 while (dq.size() >= 2 && !pts.empty() && !dq.front().contains(pts.back())) { 127 dq.pop_back(); 128 pts.pop_back(); 129 } 130 while (dq.size() >= 2 && !pts.empty() && !dq.back().contains(pts.front())) { 131 dq.pop_front(); 132 pts.pop_front(); 133 } 134 135 if (dq.size() < 3) { 136 poly.clear(); 137 return false; // empty or unbounded in our representation 138 } 139 140 // Recompute all intersection points in order (including wrap-around) 141 vector<HalfPlane> hs(dq.begin(), dq.end()); 142 poly.clear(); 143 for (size_t i = 0; i < hs.size(); ++i) { 144 size_t j = (i + 1) % hs.size(); 145 long double den = fabsl(cross(hs[i].v, hs[j].v)); 146 if (den < EPS) return false; // nearly parallel neighbors => treat as empty/degenerate 147 poly.push_back(line_intersection(hs[i], hs[j])); 148 } 149 return true; 150 } 151 152 long double polygon_area(const vector<Point>& P) { 153 long double s = 0; 154 int n = (int)P.size(); 155 for (int i = 0; i < n; ++i) { 156 int j = (i + 1) % n; 157 s += P[i].x * P[j].y - P[i].y * P[j].x; 158 } 159 return fabsl(s) * 0.5L; 160 } 161 162 int main() { 163 ios::sync_with_stdio(false); 164 cin.tie(nullptr); 165 166 // Example: Intersect five half-planes forming a pentagon-like region 167 // Constraints in ax + by <= c form 168 vector<HalfPlane> hp; 169 hp.push_back(HalfPlane::from_le( 1, 0, 5)); // x <= 5 170 hp.push_back(HalfPlane::from_le(-1, 0, 3)); // -x <= 3 => x >= -3 171 hp.push_back(HalfPlane::from_le( 0, 1, 4)); // y <= 4 172 hp.push_back(HalfPlane::from_le( 0,-1, 2)); // -y <= 2 => y >= -2 173 hp.push_back(HalfPlane::from_le( 1, 1, 6)); // x + y <= 6 174 175 vector<Point> poly; 176 bool ok = halfplane_intersection(hp, poly); 177 178 if (!ok) { 179 cout << "Empty or unbounded intersection.\n"; 180 return 0; 181 } 182 183 cout.setf(std::ios::fixed); cout << setprecision(10); 184 cout << "Vertices (counter-clockwise):\n"; 185 for (auto &pt : poly) cout << pt.x << " " << pt.y << "\n"; 186 cout << "Area: " << polygon_area(poly) << "\n"; 187 return 0; 188 } 189
This program implements a robust half-plane intersection. Half-planes are created from linear inequalities ax + by ≤ c using a left-of-line convention. The algorithm sorts by angle, removes less restrictive parallels, and maintains a deque of active constraints while pruning ends whose corner points fall outside the current half-plane. After a final cleanup, consecutive intersections form the output convex polygon. The demo constructs five constraints, computes the intersection polygon, prints vertices, and its area.
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 struct Point { long double x,y; }; 5 static const long double EPS = 1e-12L; 6 7 long double dot(const Point&a,const Point&b){return a.x*b.x+a.y*b.y;} 8 long double cross(const Point&a,const Point&b){return a.x*b.y-a.y*b.x;} 9 long double norm(const Point&a){return sqrtl(dot(a,a));} 10 Point rotCW90(const Point&a){return Point{a.y,-a.x};} 11 Point rotCCW90(const Point&a){return Point{-a.y,a.x};} 12 13 struct HalfPlane{ 14 Point p,v; long double ang; 15 HalfPlane(){} 16 HalfPlane(Point _p, Point _v){ long double L=norm(_v); v={_v.x/L,_v.y/L}; p=_p; ang=atan2l(v.y,v.x);} 17 static HalfPlane from_le(long double a,long double b,long double c){ 18 Point n{a,b}; Point v=rotCW90(n); long double n2=dot(n,n); Point p{n.x*(c/n2), n.y*(c/n2)}; return HalfPlane(p,v); 19 } 20 bool contains(const Point&x)const{ return cross(v, Point{x.x-p.x, x.y-p.y}) >= -EPS; } 21 }; 22 23 Point isect(const HalfPlane&A,const HalfPlane&B){ Point u{B.p.x-A.p.x, B.p.y-A.p.y}; long double den=cross(B.v, A.v); long double t=cross(B.v,u)/den; return Point{A.p.x + A.v.x*t, A.p.y + A.v.y*t}; } 24 25 bool halfplane_cmp(const HalfPlane&a,const HalfPlane&b){ if(fabsl(a.ang-b.ang)>1e-15L) return a.ang<b.ang; Point n=rotCCW90(a.v); long double da=n.x*a.p.x+n.y*a.p.y; long double db=n.x*b.p.x+n.y*b.p.y; return da>db; } 26 27 bool HPI(vector<HalfPlane> hp, vector<Point>& poly){ 28 sort(hp.begin(), hp.end(), halfplane_cmp); 29 vector<HalfPlane> f; f.reserve(hp.size()); 30 for(size_t i=0;i<hp.size();++i) if(i==0 || fabsl(hp[i].ang-hp[i-1].ang)>1e-15L) f.push_back(hp[i]); 31 deque<HalfPlane> dq; deque<Point> pts; 32 auto outside=[&](const HalfPlane&H,const Point&X){return !H.contains(X);} ; 33 auto pushH=[&](const HalfPlane&H){ 34 while(!dq.empty() && !pts.empty() && outside(H, pts.back())){ dq.pop_back(); pts.pop_back(); } 35 while(!dq.empty() && !pts.empty() && outside(H, pts.front())){ dq.pop_front(); pts.pop_front(); } 36 if(!dq.empty() && fabsl(cross(dq.back().v,H.v))<EPS){ dq.pop_back(); if(!pts.empty()) pts.pop_back(); } 37 if(!dq.empty() && fabsl(cross(dq.back().v,H.v))>=EPS){ pts.push_back(isect(dq.back(), H)); } 38 dq.push_back(H); 39 }; 40 for(auto&H: f) pushH(H); 41 while(dq.size()>=2 && !pts.empty() && !dq.front().contains(pts.back())){ dq.pop_back(); pts.pop_back(); } 42 while(dq.size()>=2 && !pts.empty() && !dq.back().contains(pts.front())){ dq.pop_front(); pts.pop_front(); } 43 if(dq.size()<3){ poly.clear(); return false; } 44 vector<HalfPlane> hs(dq.begin(), dq.end()); poly.clear(); 45 for(size_t i=0;i<hs.size();++i){ size_t j=(i+1)%hs.size(); long double den=fabsl(cross(hs[i].v, hs[j].v)); if(den<EPS) return false; poly.push_back(isect(hs[i], hs[j])); } 46 return true; 47 } 48 49 long double area(const vector<Point>&P){ long double s=0; int n=P.size(); for(int i=0;i<n;++i){ int j=(i+1)%n; s+=P[i].x*P[j].y-P[i].y*P[j].x; } return fabsl(s)*0.5L; } 50 51 int main(){ 52 ios::sync_with_stdio(false); cin.tie(nullptr); 53 // Input: site s and competitor sites t_i. Build Voronoi cell of s clipped by a bounding box. 54 Point s; int m; cin >> s.x >> s.y >> m; vector<Point> t(m); for(int i=0;i<m;++i) cin >> t[i].x >> t[i].y; 55 long double B = 1e6; // bounding box size (adjust as needed) 56 vector<HalfPlane> hp; 57 // Bounding box: -B <= x <= B, -B <= y <= B 58 hp.push_back(HalfPlane::from_le( 1, 0, B)); // x <= B 59 hp.push_back(HalfPlane::from_le(-1, 0, B)); // -x <= B => x >= -B 60 hp.push_back(HalfPlane::from_le( 0, 1, B)); // y <= B 61 hp.push_back(HalfPlane::from_le( 0,-1, B)); // -y <= B => y >= -B 62 63 // For each competitor t: ||x-s||^2 <= ||x-t||^2 => 2(t-s)·x <= ||t||^2 - ||s||^2 64 long double ss = s.x*s.x + s.y*s.y; 65 for(const auto& u: t){ 66 long double tt = u.x*u.x + u.y*u.y; 67 long double A = 2*(u.x - s.x); 68 long double Bc = 2*(u.y - s.y); 69 long double C = tt - ss; 70 hp.push_back(HalfPlane::from_le(A, Bc, C)); 71 } 72 73 vector<Point> cell; 74 if(!HPI(hp, cell)){ 75 cout << "Empty cell or numerical failure\n"; 76 return 0; 77 } 78 cout.setf(std::ios::fixed); cout<<setprecision(8); 79 cout << cell.size() << "\n"; 80 for(auto&p:cell) cout << p.x << " " << p.y << "\n"; 81 cout << "Area: " << area(cell) << "\n"; 82 return 0; 83 } 84
This program computes the Voronoi cell of a given site s, clipped to a large bounding box. For each competitor t, the inequality ||x−s||^2 ≤ ||x−t||^2 becomes a linear half-plane 2(t−s)·x ≤ ||t||^2 − ||s||^2. Intersecting these with the bounding box via HPI yields the polygonal cell. The output lists the vertices and area.
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 struct Point { long double x,y; }; 5 static const long double EPS = 1e-12L; 6 7 long double dot(const Point&a,const Point&b){return a.x*b.x+a.y*b.y;} 8 long double cross(const Point&a,const Point&b){return a.x*b.y-a.y*b.x;} 9 long double norm(const Point&a){return sqrtl(dot(a,a));} 10 Point rotCW90(const Point&a){return Point{a.y,-a.x};} 11 Point rotCCW90(const Point&a){return Point{-a.y,a.x};} 12 13 struct HalfPlane{ 14 Point p,v; long double ang; 15 HalfPlane(){} 16 HalfPlane(Point _p, Point _v){ long double L=norm(_v); v={_v.x/L,_v.y/L}; p=_p; ang=atan2l(v.y,v.x);} 17 static HalfPlane from_le(long double a,long double b,long double c){ 18 Point n{a,b}; Point v=rotCW90(n); long double n2=dot(n,n); Point p{n.x*(c/n2), n.y*(c/n2)}; return HalfPlane(p,v); 19 } 20 bool contains(const Point&x)const{ return cross(v, Point{x.x-p.x, x.y-p.y}) >= -EPS; } 21 }; 22 23 Point isect(const HalfPlane&A,const HalfPlane&B){ Point u{B.p.x-A.p.x, B.p.y-A.p.y}; long double den=cross(B.v, A.v); long double t=cross(B.v,u)/den; return Point{A.p.x + A.v.x*t, A.p.y + A.v.y*t}; } 24 25 bool halfplane_cmp(const HalfPlane&a,const HalfPlane&b){ if(fabsl(a.ang-b.ang)>1e-15L) return a.ang<b.ang; Point n=rotCCW90(a.v); long double da=n.x*a.p.x+n.y*a.p.y; long double db=n.x*b.p.x+n.y*b.p.y; return da>db; } 26 27 bool HPI(vector<HalfPlane> hp, vector<Point>& poly){ 28 sort(hp.begin(), hp.end(), halfplane_cmp); 29 vector<HalfPlane> f; f.reserve(hp.size()); 30 for(size_t i=0;i<hp.size();++i) if(i==0 || fabsl(hp[i].ang-hp[i-1].ang)>1e-15L) f.push_back(hp[i]); 31 deque<HalfPlane> dq; deque<Point> pts; 32 auto outside=[&](const HalfPlane&H,const Point&X){return !H.contains(X);} ; 33 auto pushH=[&](const HalfPlane&H){ 34 while(!dq.empty() && !pts.empty() && outside(H, pts.back())){ dq.pop_back(); pts.pop_back(); } 35 while(!dq.empty() && !pts.empty() && outside(H, pts.front())){ dq.pop_front(); pts.pop_front(); } 36 if(!dq.empty() && fabsl(cross(dq.back().v,H.v))<EPS){ dq.pop_back(); if(!pts.empty()) pts.pop_back(); } 37 if(!dq.empty() && fabsl(cross(dq.back().v,H.v))>=EPS){ pts.push_back(isect(dq.back(), H)); } 38 dq.push_back(H); 39 }; 40 for(auto&H: f) pushH(H); 41 while(dq.size()>=2 && !pts.empty() && !dq.front().contains(pts.back())){ dq.pop_back(); pts.pop_back(); } 42 while(dq.size()>=2 && !pts.empty() && !dq.back().contains(pts.front())){ dq.pop_front(); pts.pop_front(); } 43 if(dq.size()<3){ poly.clear(); return false; } 44 vector<HalfPlane> hs(dq.begin(), dq.end()); poly.clear(); 45 for(size_t i=0;i<hs.size();++i){ size_t j=(i+1)%hs.size(); long double den=fabsl(cross(hs[i].v, hs[j].v)); if(den<EPS) return false; poly.push_back(isect(hs[i], hs[j])); } 46 return true; 47 } 48 49 int main(){ 50 ios::sync_with_stdio(false); cin.tie(nullptr); 51 // Input: n constraints, objective c, and an optional bounding box to ensure bounded optimum. 52 int n; cin >> n; vector<long double> a(n), b(n), cst(n); 53 for(int i=0;i<n;++i) cin >> a[i] >> b[i] >> cst[i]; 54 Point c; cin >> c.x >> c.y; // objective: maximize c·x 55 56 vector<HalfPlane> hp; hp.reserve(n+4); 57 for(int i=0;i<n;++i) hp.push_back(HalfPlane::from_le(a[i], b[i], cst[i])); 58 59 // If the intersection may be unbounded, add a large bounding box (tunable parameter): 60 long double B = 1e6; // adjust based on problem scale 61 hp.push_back(HalfPlane::from_le( 1, 0, B)); 62 hp.push_back(HalfPlane::from_le(-1, 0, B)); 63 hp.push_back(HalfPlane::from_le( 0, 1, B)); 64 hp.push_back(HalfPlane::from_le( 0,-1, B)); 65 66 vector<Point> poly; 67 if(!HPI(hp, poly)){ 68 cout << "Infeasible or numerically degenerate\n"; return 0; 69 } 70 71 // Maximize c·x over convex polygon: best at a vertex 72 long double best = -numeric_limits<long double>::infinity(); 73 Point arg; for(const auto& p: poly){ long double val = dot(c, p); if(val > best){ best = val; arg = p; } } 74 75 cout.setf(std::ios::fixed); cout<<setprecision(10); 76 cout << "opt_value " << best << "\n"; 77 cout << "argmax " << arg.x << " " << arg.y << "\n"; 78 return 0; 79 } 80
This code solves a 2D linear program by converting all constraints Ax ≤ b into half-planes and intersecting them. To guarantee a bounded feasible region, it adds a large bounding box (common in practice if constraints don’t bound all directions). The optimal value of c·x over a convex polygon is attained at a vertex, so we simply scan all polygon vertices and pick the maximum.