Move an imaginary line across the plane and maintain a data structure describing what it currently touches. The continuous problem becomes a discrete sequence of events.

The skeleton

// 1. build events, sorted by the sweep coordinate
vector<Event> ev;
sort(ev.begin(), ev.end());
 
// 2. a status structure of currently-active objects
set<Segment, SweepCmp> status;
 
// 3. process
for (auto& e : ev) {
    if (e.isStart)  status.insert(e.obj);
    else            status.erase(e.obj);
    // answer whatever the event asks
}

Three design decisions: what is an event, what does the status structure hold, and what invariant does it maintain.

The classic applications

ProblemEventsStatusTime
Any two segments intersect?endpointssegments by (Shamos-Hoey)
Report all intersectionsendpoints + intersectionssegments by (Bentley-Ottmann)
Area of a union of rectanglesvertical edgessegment tree with counts
Perimeter of a union of rectanglessamesame, tracking transitions
Count rectangle overlapssamesegment tree with min-count
Closest pairpoints by set of points by
Maximum points in a windowwindow edgescount in a BIT
Skyline / building silhouettebuilding edgesmultiset of heights
Point in polygon, many queriespolygon edges + queriesactive edges
Voronoisites + circle eventsthe beach line (Fortune)
Rectangle stabbing queriesedges + queriesBIT over

Area of a union of rectangles

The template everyone should know:

// events: (x, y1, y2, +1 or -1) for the left and right edges
struct Ev { long long x, y1, y2; int type; };
sort(ev.begin(), ev.end(), [](const Ev& a, const Ev& b){ return a.x < b.x; });
 
long long area = 0, prevX = ev[0].x;
for (auto& e : ev) {
    area += covered() * (e.x - prevX);        // covered length from the segment tree
    update(e.y1, e.y2, e.type);
    prevX = e.x;
}

The segment tree stores, per node, a count of covering intervals and the covered length; covered() reads the root. Crucially, this tree never needs lazy propagation — because updates always come in matching pairs, a node’s count never goes negative and the covered length is recomputable from the count and the children.

void pull(int node, int l, int r) {
    if (cnt[node] > 0) len[node] = ys[r+1] - ys[l];
    else if (l == r)   len[node] = 0;
    else               len[node] = len[2*node] + len[2*node+1];
}

Coordinate-compress the values first.

Choosing the status structure

NeedStructure
Ordered active objects with neighboursstd::set
Count/sum over a coordinate rangeBIT or segment tree
Covered length under updatessegment tree with counts (above)
Minimum/maximum active valuemultiset or a heap with lazy deletion
-th active valuePBDS or a BIT

Radial and angular sweeps

The sweep need not be a line. Rotating a ray around a point (sorting by angle) solves:

  • visibility polygons,
  • “how many points lie in some half-plane through this point”,
  • maximum points inside an angular sector,
  • counting triangles containing the origin.

Sort by angle with the exact cross-product comparator, not atan2.

Practical notes

Event ordering at equal coordinates

When several events share a sweep coordinate, their relative order matters. For rectangle unions, process all additions before all removals at the same (or you will miss zero-width overlaps). Encode the tiebreak explicitly in the comparator rather than relying on the sort being stable.

  • Coordinate-compress early; it makes the status structure a small array.
  • Use exact integer comparators; the whole point of a sweep is that the decisions are combinatorial.
  • Think about whether the sweep should be over , over angle, or over time (see offline dynamic connectivity, which is a sweep over the query timeline).

See also: Bentley-Ottmann · Segment Intersection · Offline Query Processing