Basic Geometry - Points and Vectors
Key Points
- •A 2D point can be treated as a vector from the origin, so vector math (addition, scaling, dot, cross) applies directly to points.
- •The dot product a·b equals cos θ and measures how much one vector points in the direction of another, enabling projections and angle computations.
- •The 2D cross product a×b equals sin θ with sign, giving orientation (left/right turn) and twice the signed area of the triangle formed by the vectors.
- •Magnitudes and normalization let you work with unit directions; rotation uses a simple matrix to turn vectors by an angle.
- •Use long long for exact integer geometry (grids, counts) and long double for angles, distances, and rotation to reduce rounding errors.
- •Always compare floating-point numbers with an EPS tolerance instead of == to avoid precision bugs.
- •Orientation tests and segment intersection are classic uses of cross and dot; polygon area uses the shoelace sum of cross products.
- •Each primitive operation is O(1); processing n points (e.g., polygon area) is O(n) time and O(1) extra space.
Prerequisites
- →Coordinate geometry basics — Understanding Cartesian coordinates and lines is necessary to map algebraic operations to geometric meaning.
- →Vector algebra — Vector addition, scaling, and linear combinations underlie all point/vector operations.
- →Trigonometry (radians, sine, cosine) — Angles, rotations, and the relation of dot/cross to sine and cosine rely on trigonometric functions.
- →Floating-point arithmetic — Knowledge of rounding errors and EPS comparisons is essential for robust geometric code.
- →C++ operator overloading and structs — Implementing clean geometry primitives benefits from custom types with overloaded operators.
- →Integer overflow and numeric limits — Choosing between long long and long double and when to widen to 128-bit avoids overflow in cross products.
Detailed Explanation
Tap terms for definitions01Overview
Basic 2D geometry treats points as ordered pairs (x, y) and vectors as arrows that can be added, scaled, and compared. In competitive programming, this viewpoint turns many geometry problems into clean algebra. Vector addition moves endpoints by stacking arrows; scalar multiplication stretches or shrinks them. Two core operations are the dot product and the 2D cross product: the dot product measures directional similarity (cosine), while the cross product measures oriented area and left/right turns (sine). From these you can compute angles, projections, distances to lines, and detect intersections. Rotation is performed by a small 2×2 matrix that spins vectors around the origin by an angle θ. Because computers use finite precision, careful numeric choices matter: long long gives exact integer arithmetic for grid coordinates, and long double offers extra precision for distances and angles. EPS (a tiny tolerance) is used to compare floating numbers robustly. With these tools, you can build reliable primitives—orientation, segment intersection, projections, and polygon area—that form the backbone of many CF 1400–1800 tasks.
02Intuition & Analogies
Imagine walking on a flat field with a compass. A point (x, y) marks a location on the map; a vector is like an instruction “go 3 steps east and 4 steps north.” Adding vectors is stacking instructions back-to-back: walk 3 east and 4 north, then add 2 east and −1 north—that’s the same as one combined instruction 5 east and 3 north. Scaling a vector doubles or halves your steps, like “go twice as far in the same direction.” Now think about the dot product as how much one instruction follows another’s direction. If you face east and someone says “go northeast,” you partially comply; the dot product is large. If they say “go west,” you disobey your current heading entirely; the dot product becomes negative. That’s why the dot product finds angles and projections—how aligned are we? The 2D cross product acts like a tiny turn-signal. Place your right hand pointing along vector a and sweep toward vector b: if the turn is counterclockwise, the cross is positive; clockwise is negative. Its magnitude equals the area of the parallelogram formed by a and b, so for a triangle it’s half of that. This is why cross products detect orientation (left/right turn) and compute areas (shoelace formula). Rotation is like turning your compass dial by θ degrees while keeping your step length; the rotation matrix rotates every instruction consistently. EPS is your “fuzzy ruler” acknowledging that on a computer, 0.1 + 0.2 might not equal 0.3 exactly—so you allow a tiny slop when comparing results.
03Formal Definition
04When to Use
- Orientation and ordering: Use the cross product to determine if three points make a left turn, right turn, or are collinear—vital in convex hulls, polygon monotonicity, and angular sorts.
- Intersection tests: Combine cross and dot products to check whether two segments intersect (including endpoint touching), or whether a point lies on a segment.
- Distances and projections: Use the dot product to project a point onto a line, then compute distances to lines or segments—useful in closest pair heuristics, point-in-geometry distance queries, and reflections.
- Angles and rotations: Compute the angle between vectors using the dot product and norms, or rotate shapes for alignment and coordinate transforms.
- Areas and centroids: Use cross products to get triangle areas and the shoelace formula for polygon area and centroids, common in computational geometry tasks.
- Precision-sensitive tasks: Prefer long long when all coordinates are integers and you only need orientation/area (exactness). Switch to long double for distances, angles, and rotations. Always apply EPS when comparing floating values, especially when chaining operations (projection + intersection).
⚠️Common Mistakes
- Using == with doubles: Floating-point rounding makes direct equality unreliable. Always compare with EPS, e.g., |x| < EPS for zero, or |a-b| < EPS for equality.
- Mixing integer and floating types carelessly: Doing a cross product in int can overflow if coordinates are up to 1e9; use long long for integer geometry and long double when you need trig or distances.
- Forgetting to check degenerate cases: Projections onto a zero-length direction, collinear-but-disjoint segments, or nearly parallel lines need special handling (use EPS and guard clauses).
- Mishandling orientation signs: Remember a×b > 0 means b is to the left of a (CCW turn). Swapping vector order flips the sign.
- Not clamping when projecting to a segment: Projection onto the supporting line might fall outside the segment. Clamp the parameter t to [0,1] before computing distances to a segment.
- Angle computations with acos without clamping: Due to rounding, the cosine can be slightly outside [-1,1]. Clamp before calling acos.
- Using degrees vs radians inconsistently: C++ trig functions use radians. Convert degrees to radians when needed.
- Ignoring robustness: For polygon area and orientation with integer input, prefer exact integer cross (long long) to avoid tiny nonzero areas from floating error.
Key Formulas
Euclidean Norm
Explanation: This computes the length of a vector. It is the straight-line distance from the origin to (x, y).
Dot Product
Explanation: The dot product equals the product of magnitudes times the cosine of the angle between them. It measures how aligned the vectors are.
2D Cross Product
Explanation: This scalar measures signed area and orientation. Positive means b is to the left of a (counterclockwise), negative means right (clockwise).
Rotation Matrix
Explanation: Multiplying by this matrix rotates a vector by angle θ about the origin. Length is preserved by rotation.
Projection onto a Line
Explanation: This gives the vector from a to the perpendicular foot of p on the line through a in direction d. Add a to get the point of projection.
Orientation Test
Explanation: The sign tells whether turns left, right, or is collinear. Zero (within EPS) means collinear.
Triangle Area
Explanation: Twice the area equals the absolute cross product of the two edge vectors. The sign encodes orientation.
Shoelace Formula
Explanation: Sweep consecutive vertex cross products (with , ). The absolute value gives unsigned area.
Angle via Dot
Explanation: The angle between nonzero vectors is obtained from the cosine. Clamp the value into [-1, 1] before using arccos numerically.
Point-Line Distance
Explanation: Area of the parallelogram divided by base length equals the height, which is the perpendicular distance.
Closest Point on Segment
Explanation: Project p onto the supporting line of segment ab and clamp the parameter to [0,1]. This yields the closest point on the segment to p.
Complexity Analysis
Code Examples
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 using ld = long double; 5 const ld EPS = 1e-12L; // Tolerance for floating comparisons 6 7 int sgn(ld x) { 8 if (fabsl(x) < EPS) return 0; 9 return x < 0 ? -1 : 1; 10 } 11 12 struct Pt { 13 ld x, y; 14 Pt() : x(0), y(0) {} 15 Pt(ld _x, ld _y) : x(_x), y(_y) {} 16 17 // Vector addition and subtraction 18 Pt operator+(const Pt& o) const { return Pt(x + o.x, y + o.y); } 19 Pt operator-(const Pt& o) const { return Pt(x - o.x, y - o.y); } 20 21 // Scalar multiply and divide 22 Pt operator*(ld k) const { return Pt(x * k, y * k); } 23 Pt operator/(ld k) const { return Pt(x / k, y / k); } 24 25 // Dot and cross products 26 ld dot(const Pt& o) const { return x * o.x + y * o.y; } 27 ld cross(const Pt& o) const { return x * o.y - y * o.x; } 28 29 // Squared norm and norm (magnitude) 30 ld norm2() const { return this->dot(*this); } 31 ld norm() const { return sqrtl(norm2()); } 32 33 // Unit vector (normalization); returns (0,0) if vector is ~0 34 Pt unit() const { 35 ld n = norm(); 36 if (n < EPS) return Pt(0, 0); 37 return (*this) / n; 38 } 39 40 // Perpendicular vector (rotate by +90 degrees) 41 Pt perp() const { return Pt(-y, x); } 42 43 // Rotate by angle theta (radians) around origin 44 Pt rotate(ld theta) const { 45 ld c = cosl(theta), s = sinl(theta); 46 return Pt(x * c - y * s, x * s + y * c); 47 } 48 }; 49 50 // Angle between two nonzero vectors, in radians, robustly clamped 51 ld angle_between(const Pt& a, const Pt& b) { 52 ld na = a.norm(), nb = b.norm(); 53 if (na < EPS || nb < EPS) return 0; // undefined; return 0 as a safe default 54 ld cosv = a.dot(b) / (na * nb); 55 cosv = max((ld)-1.0, min((ld)1.0, cosv)); 56 return acosl(cosv); 57 } 58 59 int main() { 60 ios::sync_with_stdio(false); 61 cin.tie(nullptr); 62 63 Pt a(3, 4), b(1, -2); 64 65 // Demonstrate basic operations 66 Pt c = a + b; // (4, 2) 67 Pt d = a - b; // (2, 6) 68 Pt e = a * 2; // (6, 8) 69 ld dot_ab = a.dot(b); // 3*1 + 4*(-2) = -5 70 ld cross_ab = a.cross(b); // 3*(-2) - 4*1 = -10 71 ld len_a = a.norm(); // 5 72 Pt ua = a.unit(); // (0.6, 0.8) 73 74 // Rotation by 90 degrees (pi/2 radians) 75 Pt r = a.rotate(acosl(-1)/2); 76 77 cout.setf(ios::fixed); cout << setprecision(10); 78 cout << "c=(" << c.x << "," << c.y << ")\n"; 79 cout << "d=(" << d.x << "," << d.y << ")\n"; 80 cout << "e=(" << e.x << "," << e.y << ")\n"; 81 cout << "dot(a,b)=" << dot_ab << ", cross(a,b)=" << cross_ab << "\n"; 82 cout << "|a|=" << len_a << ", unit(a)=(" << ua.x << "," << ua.y << ")\n"; 83 cout << "a rotated 90 deg = (" << r.x << "," << r.y << ")\n"; 84 cout << "angle(a,b) rad = " << angle_between(a,b) << "\n"; 85 86 return 0; 87 } 88
This program defines a Pt struct for 2D vectors/points using long double. It implements vector arithmetic, dot and cross products, length, unit vector, perpendicular, and rotation. It demonstrates usage and prints results with high precision. The angle_between function clamps the cosine before calling acosl to avoid NaN from rounding. EPS guards normalization and equality-like comparisons.
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 using ld = long double; 5 const ld EPS = 1e-12L; 6 7 int sgn(ld x){ if (fabsl(x) < EPS) return 0; return x < 0 ? -1 : 1; } 8 9 struct Pt{ ld x,y; Pt(){} Pt(ld X, ld Y):x(X),y(Y){} Pt operator+(const Pt&o)const{return {x+o.x,y+o.y};} Pt operator-(const Pt&o)const{return {x-o.x,y-o.y};} ld cross(const Pt&o)const{return x*o.y - y*o.x;} ld dot(const Pt&o)const{return x*o.x + y*o.y;} }; 10 11 ld orient(const Pt& a, const Pt& b, const Pt& c){ 12 return (b-a).cross(c-a); // >0: CCW, <0: CW, ~0: collinear 13 } 14 15 bool onSegment(const Pt& a, const Pt& b, const Pt& p){ 16 // Check collinearity and bounding box (with EPS) 17 if (sgn(orient(a,b,p)) != 0) return false; 18 ld minx = min(a.x,b.x) - EPS, maxx = max(a.x,b.x) + EPS; 19 ld miny = min(a.y,b.y) - EPS, maxy = max(a.y,b.y) + EPS; 20 return (p.x >= minx && p.x <= maxx && p.y >= miny && p.y <= maxy); 21 } 22 23 bool segmentsIntersect(const Pt& p1, const Pt& p2, const Pt& q1, const Pt& q2){ 24 ld o1 = orient(p1,p2,q1); 25 ld o2 = orient(p1,p2,q2); 26 ld o3 = orient(q1,q2,p1); 27 ld o4 = orient(q1,q2,p2); 28 29 int s1=sgn(o1), s2=sgn(o2), s3=sgn(o3), s4=sgn(o4); 30 31 // Proper intersection if orientations differ strictly 32 if (s1*s2 < 0 && s3*s4 < 0) return true; 33 34 // Handle collinear cases (touching endpoints or overlap) 35 if (s1==0 && onSegment(p1,p2,q1)) return true; 36 if (s2==0 && onSegment(p1,p2,q2)) return true; 37 if (s3==0 && onSegment(q1,q2,p1)) return true; 38 if (s4==0 && onSegment(q1,q2,p2)) return true; 39 40 return false; 41 } 42 43 int main(){ 44 ios::sync_with_stdio(false); 45 cin.tie(nullptr); 46 47 Pt p1(0,0), p2(2,0); 48 Pt q1(1,-1), q2(1,1); 49 Pt r1(3,0), r2(5,0); 50 51 cout << boolalpha; 52 cout << "[p1-p2] with [q1-q2] intersect? " << segmentsIntersect(p1,p2,q1,q2) << "\n"; // true (cross) 53 cout << "[p1-p2] with [r1-r2] intersect? " << segmentsIntersect(p1,p2,r1,r2) << "\n"; // false (disjoint) 54 55 // Collinear overlap example 56 Pt s1(0,0), s2(4,0), t1(2,0), t2(6,0); 57 cout << "[s1-s2] with [t1-t2] intersect? " << segmentsIntersect(s1,s2,t1,t2) << "\n"; // true (overlap) 58 59 return 0; 60 } 61
This code implements orientation and a robust closed-segment intersection test. It first checks proper intersection via opposite orientation signs. Then it handles all collinear edge cases with an EPS-extended bounding-box test. This approach works reliably for typical CF inputs with floating coordinates and also tolerates tiny numeric errors.
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 using ld = long double; 5 const ld EPS = 1e-12L; 6 7 struct Pt{ ld x,y; Pt(){} Pt(ld X, ld Y):x(X),y(Y){} Pt operator+(const Pt&o)const{return {x+o.x,y+o.y};} Pt operator-(const Pt&o)const{return {x-o.x,y-o.y};} Pt operator*(ld k)const{return {x*k,y*k};} ld dot(const Pt&o)const{return x*o.x + y*o.y;} ld cross(const Pt&o)const{return x*o.y - y*o.x;} }; 8 9 Pt projectPointLine(const Pt& a, const Pt& b, const Pt& p){ 10 Pt d = b - a; 11 ld denom = d.dot(d); 12 if (denom < EPS) return a; // a==b: treat as a point 13 ld t = (p - a).dot(d) / denom; // unbounded parameter 14 return a + d * t; 15 } 16 17 Pt closestPointSegment(const Pt& a, const Pt& b, const Pt& p){ 18 Pt d = b - a; 19 ld denom = d.dot(d); 20 if (denom < EPS) return a; // segment is a point 21 ld t = (p - a).dot(d) / denom; 22 t = max((ld)0, min((ld)1, t)); // clamp to segment 23 return a + d * t; 24 } 25 26 ld distPointLine(const Pt& a, const Pt& b, const Pt& p){ 27 Pt d = b - a; 28 if (fabsl(d.x) < EPS && fabsl(d.y) < EPS) return hypotl(p.x - a.x, p.y - a.y); 29 // area = |d x (p-a)|, height = area / |d| 30 return fabsl(d.cross(p - a)) / sqrtl(d.dot(d)); 31 } 32 33 ld distPointSegment(const Pt& a, const Pt& b, const Pt& p){ 34 Pt c = closestPointSegment(a,b,p); 35 return hypotl(p.x - c.x, p.y - c.y); 36 } 37 38 int main(){ 39 ios::sync_with_stdio(false); 40 cin.tie(nullptr); 41 42 Pt a(0,0), b(4,0); 43 Pt p(3,2); 44 Pt q(5,3); 45 46 Pt proj = projectPointLine(a,b,p); 47 Pt cp1 = closestPointSegment(a,b,p); 48 Pt cp2 = closestPointSegment(a,b,q); 49 50 cout.setf(ios::fixed); cout << setprecision(10); 51 cout << "Projection of p onto line ab: (" << proj.x << "," << proj.y << ")\n"; 52 cout << "Closest point to p on segment ab: (" << cp1.x << "," << cp1.y << ")\n"; 53 cout << "Closest point to q on segment ab: (" << cp2.x << "," << cp2.y << ")\n"; 54 cout << "dist(p, line ab) = " << distPointLine(a,b,p) << "\n"; 55 cout << "dist(p, seg ab) = " << distPointSegment(a,b,p) << "\n"; 56 cout << "dist(q, seg ab) = " << distPointSegment(a,b,q) << "\n"; 57 58 return 0; 59 } 60
Using dot and cross, we compute the projection of a point onto a line, clamp to get the closest point on a segment, and then compute distances. Degenerate segments (a=b) are handled explicitly. Cross over norm yields the perpendicular distance to a line, which is numerically stable and avoids expensive angle computations.
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 using ll = long long; // exact integer area*2 if using integer coords 5 using ld = long double; // for floating output when desired 6 7 struct IPoint { ll x, y; }; 8 9 // Returns twice the signed area using 64-bit exact arithmetic 10 long long signedArea2(const vector<IPoint>& p){ 11 int n = (int)p.size(); 12 __int128 s = 0; // widen to avoid overflow if inputs are up to ~1e9 13 for(int i=0;i<n;i++){ 14 int j = (i+1)%n; 15 s += (__int128)p[i].x * p[j].y - (__int128)p[i].y * p[j].x; 16 } 17 long long r = (long long)s; // assumes final value fits in 64-bit; adjust if necessary 18 return r; // may be negative if CW order 19 } 20 21 int polygonOrientation(const vector<IPoint>& p){ 22 long long a2 = signedArea2(p); 23 if (a2 == 0) return 0; // collinear or degenerate polygon 24 return a2 > 0 ? +1 : -1; // +1: CCW, -1: CW 25 } 26 27 int main(){ 28 ios::sync_with_stdio(false); 29 cin.tie(nullptr); 30 31 vector<IPoint> poly = { {0,0}, {4,0}, {4,3}, {0,3} }; // rectangle 32 long long a2 = signedArea2(poly); 33 34 cout << "signed area * 2 = " << a2 << "\n"; 35 cout << "unsigned area = " << fixed << setprecision(1) << ( (long double) llabs(a2) / 2.0L ) << "\n"; 36 37 int ori = polygonOrientation(poly); 38 cout << "orientation = " << (ori>0?"CCW": (ori<0?"CW":"degenerate")) << "\n"; 39 40 return 0; 41 } 42
For integer coordinates, the shoelace sum can be accumulated exactly in 64-bit (optionally widening to 128-bit during accumulation to prevent overflow). signedArea2 returns twice the signed area, which is an integer. The sign indicates polygon orientation (CCW positive). This is robust and avoids floating error entirely for grid-aligned inputs.