Polygon Area

For a simple polygon with vertices in order:

with indices mod . The signed value is positive for counter-clockwise order.

ll area2(const vector<P>& p) {                        // TWICE the signed area
    int n = p.size();
    ll s = 0;
    for (int i = 0; i < n; i++) s += cross(p[i], p[(i + 1) % n]);
    return s;                                          // > 0 means CCW
}
double area(const vector<P>& p) { return fabs((double)area2(p)) / 2.0; }
 
PD centroid(const vector<P>& p) {                      // area-weighted centroid of the region
    int n = p.size();
    double cx = 0, cy = 0, a2 = area2(p);
    for (int i = 0; i < n; i++) {
        ll c = cross(p[i], p[(i + 1) % n]);
        cx += (double)(p[i].x + p[(i + 1) % n].x) * c;
        cy += (double)(p[i].y + p[(i + 1) % n].y) * c;
    }
    return {cx / (3 * a2), cy / (3 * a2)};
}

Keep twice the area

is always an integer for integer coordinates. Work with it throughout, halve only when printing, and you never touch floating point.

What the sign gives you

  • counter-clockwise; clockwise; degenerate.
  • Normalise once at input: if (area2(p) < 0) reverse(p.begin(), p.end());

Many algorithms (point-in-polygon, hull merging, Minkowski sums) silently assume CCW.

Requirements

The polygon must be simple — no self-intersections. For a self-intersecting polygon the formula returns a signed sum with regions counted by winding number, which is occasionally what you want but usually is not.

QuantityFormula
Perimeter
Centroid
Pick’s theorem for a lattice polygon
Boundary lattice points
Interior lattice points
Is it convex?all orientations agree
Bounding boxmin/max of the coordinates

The centroid formula is the area-weighted one (the centre of mass of the region), not the average of the vertices. The two differ for non-uniform vertex spacing, and problems usually mean the former.

Triangle area

Also useful: Heron’s formula with the semiperimeter — but it is numerically unstable for thin triangles. Prefer the cross product when coordinates are known.

Area of a union of shapes

ShapesMethod
Union of rectanglessweep line + segment tree with counts,
Union of circlesangular sweep on each circle’s boundary, — 13-circle-union-and-angular-sweep
Union of general polygonspolygon clipping (Weiler-Atherton, or a Boost/Clipper library) — 11-polygon-clipping-and-union
Intersection of two convex polygons by simultaneous traversal, or half-plane intersection — 09-half-plane-intersection
Intersection of a convex polygon and a half-planeSutherland-Hodgman clipping,
Circle-polygon intersection areasum the signed areas of circle-triangle pieces per edge

Sutherland-Hodgman clipping

vector<PD> clipLeft(const vector<PD>& poly, P a, P b) {  // keep the side with orient(a,b,*) >= 0
    vector<PD> res;
    int n = poly.size();
    for (int i = 0; i < n; i++) {
        PD cur = poly[i], nxt = poly[(i + 1) % n];
        double s1 = cross(toPD(b - a), cur - toPD(a)), s2 = cross(toPD(b - a), nxt - toPD(a));
        bool in1 = s1 >= 0, in2 = s2 >= 0;
        if (in1) res.push_back(cur);
        if (in1 != in2) {                              // interpolate the crossing point
            double t = s1 / (s1 - s2);
            res.push_back(cur + (nxt - cur) * t);
        }
    }
    return res;
}

Clipping a convex polygon by each of another’s edges gives their intersection in — enough for most problems; the full clipping/union toolkit (including non-convex clip windows) is its own note.

Pick’s Theorem

For a simple polygon whose vertices are lattice points:

where is the number of interior lattice points and the number of lattice points on the boundary.

Computing the three quantities

Area — shoelace, exactly (see above). Boundary points — each edge from to contributes lattice points, counting one endpoint per edge:

ll boundaryPoints(const vector<P>& p) {
    int n = p.size(); ll B = 0;
    for (int i = 0; i < n; i++) { P d = p[(i + 1) % n] - p[i]; B += __gcd(llabs(d.x), llabs(d.y)); }
    return B;
}

Interior points — rearrange Pick: , computed from area2 and B with no floating point:

ll interiorPoints(ll twiceArea, ll B) { return (llabs(twiceArea) - B + 2) / 2; }

What it is for

ProblemUse
Count lattice points inside a polygon
Count lattice points on the boundarythe gcd sum
Count all lattice points in a polygon
Verify a polygon’s area from lattice countsPick’s formula
Lattice points in a trianglePick, or a direct gcd argument
Lattice points under a line (floor sums)a different technique — see below

What it does not do

  • 3D has no analogue. Reeve tetrahedra (e.g. vertices for varying ) have the same lattice-point counts (no interior or extra boundary points, for any ) but volume growing with , so no Pick-style formula exists in three dimensions.
  • Non-lattice vertices break it entirely.
  • Self-intersecting polygons break it (the “area” is not well defined).
  • Polygons with holes: where is the number of holes (an Euler-characteristic correction — each hole is an extra boundary component, decreasing by exactly one more, by the same gluing argument run in reverse: cutting a hole out of a disk is one fewer “glue”).

Pick’s theorem does not handle “count ”. That is the floor sum:

ll floorSum(ll n, ll m, ll a, ll b) {
    ll ans = 0;
    if (a >= m) { ans += (n - 1) * n / 2 * (a / m); a %= m; }
    if (b >= m) { ans += n * (b / m); b %= m; }
    ll yMax = (a * n + b) / m;
    if (yMax == 0) return ans;
    ans += yMax * (n - (m * yMax - b + a - 1) / a);
    ans += floorSum(yMax, a, m, (a - (m * yMax - b) % a) % a);
    return ans;
}

by a Euclidean-style recursion — the standard tool for counting lattice points under a line, and a good companion to Pick’s theorem.

Worked example

The triangle :

  • , so ;
  • boundary: ;
  • .

The interior points are — three, as predicted.

Point in Polygon

General (possibly non-convex) polygon —

Ray casting: shoot a horizontal ray from and count edge crossings using `crosses`. Odd = inside.

// returns 1 inside, 0 outside, -1 on the boundary
int inPolygon(P p, const vector<P>& poly) {
    int n = poly.size(), cnt = 0;
    for (int i = 0; i < n; i++) {
        P a = poly[i], b = poly[(i + 1) % n];
        if (orient(a, b, p) == 0 && onSegment(p, a, b)) return -1;    // on an edge
        if (crosses(p, a, b)) cnt++;
    }
    return cnt & 1;
}

Everything here is exact integer arithmetic; there is no epsilon and no intersection point. Correctness of the half-open convention in crosses is proved in Lines and Segments; summed over a closed polygon, the parity of the crossing count is the even-odd rule — a ray from a point outside the polygon crosses the boundary an even number of times (it starts and ends outside the same unbounded region), while a ray from an interior point crosses an odd number of times (it must leave the bounded region exactly once more than it re-enters, since it never returns to ).

Convex polygon, many queries —

With the polygon given CCW, binary search the angular sector from vertex 0:

// poly is CCW, poly[0] is the lowest-then-leftmost vertex
int inConvex(P p, const vector<P>& poly) {
    int n = poly.size();
    int o1 = orient(poly[0], poly[1], p);
    if (o1 < 0) return 0;
    if (o1 == 0) return onSegment(p, poly[0], poly[1]) ? -1 : 0;      // on ray poly[0]->poly[1]
 
    int o2 = orient(poly[0], poly[n - 1], p);
    if (o2 > 0) return 0;
    if (o2 == 0) return onSegment(p, poly[0], poly[n - 1]) ? -1 : 0;  // on the closing edge
 
    int lo = 1, hi = n - 1;
    while (hi - lo > 1) {                              // find the wedge containing p
        int mid = (lo + hi) / 2;
        if (orient(poly[0], poly[mid], p) >= 0) lo = mid; else hi = mid;
    }
    int o = orient(poly[lo], poly[lo + 1], p);
    if (o < 0) return 0;
    if (o == 0) return -1;                             // on the boundary
    return 1;
}

Preprocess by rotating the polygon so poly[0] is the bottom-most (then left-most) vertex. Full tangent/extreme-point machinery on convex polygons is in Convex Polygon Queries.

A naive version of this check silently misclassifies boundary points

Writing just if (orient(poly[0],poly[1],p) < 0) return 0; and if (orient(poly[0],poly[n-1],p) > 0) return 0; and then always running the binary search is wrong: when lies exactly on the closing edge (so ), every midpoint test in the search still returns >= 0 (since ‘s direction from poly[0] coincides with poly[n-1]’s, which is more CCW than any poly[mid] for ), driving the search to the wedge instead of the true closing edge — and the final orientation test against the wrong edge can come out strictly positive, reporting a boundary point as interior. The explicit / branches above route these two boundary rays to a direct onSegment check before the search ever runs.

Winding number — the alternative

Sum the signed angle subtended by each edge. Non-zero winding means inside. Equivalent to ray casting for simple polygons, but differs for self-intersecting ones: ray casting uses the even-odd rule, winding uses the non-zero rule. Graphics APIs let you choose; contest problems almost always mean simple polygons, where they agree.

An exact integer version counts upward and downward crossings separately rather than summing angles.

The decision table

SituationMethodCost
One query, any polygonray casting
Many queries, convexangular binary search
Many queries, non-convextriangulate + point location, or a sweep after
Many queries, offlinesort queries by and sweep
Point in a trianglethree orientation tests, or barycentric coordinates
Point in a circlecompare squared distances
Point in a half-planeone orientation test

Point in a triangle

bool inTriangle(P p, P a, P b, P c) {
    int d1 = orient(a, b, p), d2 = orient(b, c, p), d3 = orient(c, a, p);
    bool hasNeg = (d1 < 0) || (d2 < 0) || (d3 < 0);
    bool hasPos = (d1 > 0) || (d2 > 0) || (d3 > 0);
    return !(hasNeg && hasPos);                        // all the same sign (or zero)
}

Boundary handling

Decide up front whether “on the boundary” counts as inside, and make it explicit in the return value (as the -1 above). Problems differ, and a silent choice is a silent bug.

QueryMethod
Is the polygon convex?all orientations agree
Is a segment inside the polygon?both endpoints inside and no proper crossing with any edge
Is one polygon inside another?all vertices inside and no edge crossings
Nearest boundary pointminimum over distToSegment for each edge
Which triangle of a triangulation contains ?point location (a trapezoidal map, or a KD-tree) — 18-point-location-and-counting

Segment in Polygon

Whether a segment lies entirely inside a simple polygon (touching the boundary is allowed), for any polygon shape. A proper crossing with an edge means the segment leaves the polygon. Otherwise collect every parameter at which the segment touches the boundary (polygon vertices on it, endpoints of edges on it), sort them, and test the midpoint of every consecutive pair with point-in-polygon: between two touch points the segment is entirely inside or entirely outside (it cannot cross the boundary without a proper crossing, which was already ruled out, or without passing through a recorded touch point).

bool segmentInPolygon(const vector<P>& poly, P A, P B) {
    int n = poly.size();
    if (inPolygon(A, poly) == 0 || inPolygon(B, poly) == 0) return false;
    vector<double> ts = {0, 1};
    P d = B - A; double len2 = norm2(d);
    auto param = [&](P p) { return len2 == 0 ? 0.0 : (double)dot(p - A, d) / len2; };
    for (int i = 0; i < n; i++) {
        P u = poly[i], v = poly[(i + 1) % n];
        int o1 = orient(A, B, u), o2 = orient(A, B, v), o3 = orient(u, v, A), o4 = orient(u, v, B);
        if (o1 * o2 < 0 && o3 * o4 < 0) return false;    // proper crossing: leaves the polygon
        if (o1 == 0 && onSegment(u, A, B)) ts.push_back(param(u));
        if (o2 == 0 && onSegment(v, A, B)) ts.push_back(param(v));
    }
    sort(ts.begin(), ts.end());
    for (int i = 0; i + 1 < (int)ts.size(); i++) {
        if (ts[i + 1] - ts[i] < 1e-12) continue;
        PD mid = toPD(A) + toPD(d) * ((ts[i] + ts[i + 1]) / 2);
        P midi = {llround(mid.x), llround(mid.y)};        // midpoint may be irrational in general; see note below
        if (inPolygon(midi, poly) == 0) return false;
    }
    return true;
}

Complexity: . (The rounding of the midpoint to the nearest lattice point is safe only when it is far from the boundary — i.e. between two genuinely distinct touch parameters with no boundary vertex between; for an exact check without that caveat, test the midpoint against the polygon’s edges directly with floating-point inPolygon-style orientation rather than rounding back to P.)