Sweep Line Algorithms

Sweep line algorithms process geometric objects by moving a conceptual line across the plane. These techniques efficiently solve problems involving intersections, closest pairs, and convex hulls.

Sweep Line Concept

A sweep line algorithm processes geometric objects by conceptually moving a line (usually vertical) across the plane from left to right, maintaining active objects and processing events as they occur.

Key Components:

  • Event Points: Critical positions where sweep line stops
  • Event Queue: Ordered list of events to process
  • Active Structure: Objects currently intersecting sweep line
  • Sweep Invariant: Properties maintained during sweep

Basic Sweep Line Template


struct Event {
    double x; // X-coordinate of event
    int type; // Type of event (start, end, intersection, etc.)
    int id;   // Associated object ID
    
    bool operator<(const Event& other) const {
        if(abs(x - other.x) > EPS) return x < other.x;
        return type < other.type; // Process by type if same x
    }
};

void sweepLine(vector<Event>& events) {
    sort(events.begin(), events.end());
    
    set<ActiveObject> activeSet; // Maintain active objects
    
    for(Event& event : events) {
        // Process event based on type
        if(event.type == START) {
            activeSet.insert(getObject(event.id));
        } else if(event.type == END) {
            activeSet.erase(getObject(event.id));
        }
        
        // Check for interactions between active objects
        processActiveObjects(activeSet, event.x);
    }
}
                    

Line Segment Intersection

Find all intersections among n line segments using sweep line:


struct Segment {
    Point start, end;
    int id;
    
    // Get y-coordinate at given x
    double getY(double x) const {
        if(abs(start.x - end.x) < EPS) return start.y; // Vertical line
        double t = (x - start.x) / (end.x - start.x);
        return start.y + t * (end.y - start.y);
    }
    
    bool operator<(const Segment& other) const {
        // For sweep line ordering at current x
        return getY(currentX) < other.getY(currentX);
    }
};

vector<Point> findIntersections(vector<Segment>& segments) {
    vector<Event> events;
    vector<Point> intersections;
    
    // Create start and end events for each segment
    for(int i = 0; i < segments.size(); i++) {
        Segment& seg = segments[i];
        if(seg.start.x > seg.end.x) swap(seg.start, seg.end);
        
        events.push_back({seg.start.x, START, i});
        events.push_back({seg.end.x, END, i});
    }
    
    sort(events.begin(), events.end());
    set<Segment> activeSegments;
    
    for(Event& event : events) {
        currentX = event.x; // Global variable for ordering
        
        if(event.type == START) {
            Segment& seg = segments[event.id];
            auto it = activeSegments.insert(seg).first;
            
            // Check intersections with neighbors
            if(it != activeSegments.begin()) {
                auto prev = prev(it);
                Point inter;
                if(segmentIntersection(*prev, *it, inter)) {
                    intersections.push_back(inter);
                }
            }
            
            auto next = next(it);
            if(next != activeSegments.end()) {
                Point inter;
                if(segmentIntersection(*it, *next, inter)) {
                    intersections.push_back(inter);
                }
            }
            
        } else if(event.type == END) {
            Segment& seg = segments[event.id];
            auto it = activeSegments.find(seg);
            
            // Check if neighbors now intersect
            if(it != activeSegments.begin() && next(it) != activeSegments.end()) {
                auto prev_it = prev(it);
                auto next_it = next(it);
                Point inter;
                if(segmentIntersection(*prev_it, *next_it, inter)) {
                    intersections.push_back(inter);
                }
            }
            
            activeSegments.erase(it);
        }
    }
    
    return intersections;
}
                    

Convex Hull (Graham Scan)

Construct convex hull using polar angle sweep:


// Cross product for orientation test
double cross(Point O, Point A, Point B) {
    return (A.x - O.x) * (B.y - O.y) - (A.y - O.y) * (B.x - O.x);
}

// Distance squared between two points
double distSq(Point A, Point B) {
    return (A.x - B.x) * (A.x - B.x) + (A.y - B.y) * (A.y - B.y);
}

vector<Point> convexHull(vector<Point> points) {
    int n = points.size();
    if(n < 3) return points;
    
    // Find bottom-most point (and leftmost if tie)
    int minIdx = 0;
    for(int i = 1; i < n; i++) {
        if(points[i].y < points[minIdx].y || 
           (points[i].y == points[minIdx].y && points[i].x < points[minIdx].x)) {
            minIdx = i;
        }
    }
    swap(points[0], points[minIdx]);
    Point pivot = points[0];
    
    // Sort by polar angle with respect to pivot
    sort(points.begin() + 1, points.end(), [&](Point A, Point B) {
        double crossProd = cross(pivot, A, B);
        if(abs(crossProd) < EPS) {
            // Collinear points - sort by distance
            return distSq(pivot, A) < distSq(pivot, B);
        }
        return crossProd > 0; // Counter-clockwise order
    });
    
    // Remove collinear points at the end (keep only farthest)
    int m = 1;
    for(int i = 1; i < n; i++) {
        while(i < n - 1 && abs(cross(pivot, points[i], points[i + 1])) < EPS) {
            i++;
        }
        points[m++] = points[i];
    }
    
    if(m < 3) return points; // Not enough points for hull
    
    // Build convex hull
    vector<Point> hull;
    hull.push_back(points[0]);
    hull.push_back(points[1]);
    hull.push_back(points[2]);
    
    for(int i = 3; i < m; i++) {
        // Remove points that create clockwise turn
        while(hull.size() > 1 && 
              cross(hull[hull.size()-2], hull[hull.size()-1], points[i]) <= 0) {
            hull.pop_back();
        }
        hull.push_back(points[i]);
    }
    
    return hull;
}
                    

Rectangle Union Area

Calculate the area covered by union of rectangles:


struct Rectangle {
    double x1, y1, x2, y2;
    Rectangle(double x1, double y1, double x2, double y2) 
        : x1(x1), y1(y1), x2(x2), y2(y2) {}
};

double rectangleUnionArea(vector<Rectangle>& rects) {
    vector<Event> events;
    
    // Create events for rectangle starts and ends
    for(int i = 0; i < rects.size(); i++) {
        events.push_back({rects[i].x1, START, i});
        events.push_back({rects[i].x2, END, i});
    }
    
    sort(events.begin(), events.end());
    
    double totalArea = 0;
    vector<pair<double, double>> activeIntervals;
    double lastX = 0;
    
    for(int i = 0; i < events.size(); i++) {
        double currentX = events[i].x;
        
        // Calculate contribution of current active intervals
        if(i > 0) {
            double width = currentX - lastX;
            double height = getUnionHeight(activeIntervals);
            totalArea += width * height;
        }
        
        // Process all events at current x-coordinate
        while(i < events.size() && abs(events[i].x - currentX) < EPS) {
            if(events[i].type == START) {
                Rectangle& rect = rects[events[i].id];
                activeIntervals.push_back({rect.y1, rect.y2});
            } else {
                Rectangle& rect = rects[events[i].id];
                activeIntervals.erase(
                    find(activeIntervals.begin(), activeIntervals.end(), 
                         make_pair(rect.y1, rect.y2))
                );
            }
            i++;
        }
        i--; // Adjust for outer loop increment
        
        lastX = currentX;
    }
    
    return totalArea;
}

// Helper function to calculate union of 1D intervals
double getUnionHeight(vector<pair<double, double>>& intervals) {
    if(intervals.empty()) return 0;
    
    vector<pair<double, double>> sorted = intervals;
    sort(sorted.begin(), sorted.end());
    
    double totalHeight = 0;
    double currentStart = sorted[0].first;
    double currentEnd = sorted[0].second;
    
    for(int i = 1; i < sorted.size(); i++) {
        if(sorted[i].first <= currentEnd) {
            // Overlapping intervals
            currentEnd = max(currentEnd, sorted[i].second);
        } else {
            // Non-overlapping interval
            totalHeight += currentEnd - currentStart;
            currentStart = sorted[i].first;
            currentEnd = sorted[i].second;
        }
    }
    
    totalHeight += currentEnd - currentStart;
    return totalHeight;
}
                    

Closest Pair of Points

Find the closest pair among n points efficiently:


double closestPairSweep(vector<Point>& points) {
    vector<pair<Point, int>> events; // Point and original index
    
    for(int i = 0; i < points.size(); i++) {
        events.push_back({points[i], i});
    }
    
    // Sort by x-coordinate
    sort(events.begin(), events.end(), [](const auto& a, const auto& b) {
        return a.first.x < b.first.x;
    });
    
    double minDist = 1e18;
    set<pair<double, int>> activePoints; // y-coordinate, index
    int left = 0;
    
    for(int right = 0; right < events.size(); right++) {
        Point currentPoint = events[right].first;
        
        // Remove points that are too far in x-direction
        while(left < right && 
              currentPoint.x - events[left].first.x >= minDist) {
            activePoints.erase({events[left].first.y, events[left].second});
            left++;
        }
        
        // Check distances to nearby points in y-direction
        auto lower = activePoints.lower_bound({currentPoint.y - minDist, -1});
        auto upper = activePoints.upper_bound({currentPoint.y + minDist, INT_MAX});
        
        for(auto it = lower; it != upper; ++it) {
            Point otherPoint = points[it->second];
            double dist = sqrt(pow(currentPoint.x - otherPoint.x, 2) + 
                              pow(currentPoint.y - otherPoint.y, 2));
            minDist = min(minDist, dist);
        }
        
        activePoints.insert({currentPoint.y, events[right].second});
    }
    
    return minDist;
}
                    

Sweep Line Tips

  • Always handle degenerate cases (vertical lines, overlapping points)
  • Use appropriate data structures for the active set (usually balanced BST)
  • Be careful with floating-point precision in geometric computations
  • Sort events properly - consider event type when x-coordinates are equal

CodeForces Challenge Problems

Practice sweep line algorithms:

Hard

Line Sweep

Practice sweep line technique for geometric problems.

Sweep Line Geometry
Solve Problem