扫描线算法

扫描线算法是计算几何中的强大技术,通过在平面上扫描一条线来处理几何对象。该方法以最优时间复杂度高效解决涉及区间、线段和几何相交的问题。

线段相交检测

使用从左到右移动的扫描线找出n条线段中的所有相交。

线段相交检测

#include <iostream>
#include <vector>
#include <set>
#include <algorithm>
#include <map>
using namespace std;

struct Point {
    double x, y;
    Point(double x = 0, double y = 0) : x(x), y(y) {}
    Point operator-(const Point& p) const { return Point(x - p.x, y - p.y); }
    double cross(const Point& p) const { return x * p.y - y * p.x; }
};

struct Segment {
    Point a, b;
    int id;
    Segment(Point a, Point b, int id) : a(a), b(b), id(id) {
        if (a.x > b.x) swap(this->a, this->b);
    }
};

struct Event {
    double x;
    int type; // 0: 开始, 1: 结束
    int segId;
    
    bool operator<(const Event& other) const {
        if (x != other.x) return x < other.x;
        return type < other.type; // 先处理开始事件
    }
};

class SweepLine {
private:
    vector<Segment> segments;
    vector<Event> events;
    
    double getY(const Segment& seg, double x) {
        if (seg.a.x == seg.b.x) return seg.a.y;
        double t = (x - seg.a.x) / (seg.b.x - seg.a.x);
        return seg.a.y + t * (seg.b.y - seg.a.y);
    }
    
    bool intersect(const Segment& s1, const Segment& s2) {
        Point d1 = s1.b - s1.a;
        Point d2 = s2.b - s2.a;
        Point d3 = s2.a - s1.a;
        
        double cross1 = d1.cross(d2);
        if (abs(cross1) < 1e-9) return false; // 平行
        
        double t1 = d3.cross(d2) / cross1;
        double t2 = d3.cross(d1) / cross1;
        
        return t1 >= 0 && t1 <= 1 && t2 >= 0 && t2 <= 1;
    }
    
public:
    void addSegment(Point a, Point b) {
        segments.push_back(Segment(a, b, segments.size()));
        events.push_back({min(a.x, b.x), 0, (int)segments.size() - 1});
        events.push_back({max(a.x, b.x), 1, (int)segments.size() - 1});
    }
    
    vector<pair<int, int>> findIntersections() {
        sort(events.begin(), events.end());
        
        auto cmp = [&](int a, int b) {
            double x = events.empty() ? 0 : events[0].x;
            return getY(segments[a], x) < getY(segments[b], x);
        };
        
        set<int, decltype(cmp)> active(cmp);
        vector<pair<int, int>> intersections;
        
        for (const Event& e : events) {
            if (e.type == 0) { // 开始事件
                auto it = active.insert(e.segId).first;
                
                // 检查与邻居的相交
                if (it != active.begin()) {
                    auto prev = prev(it);
                    if (intersect(segments[*prev], segments[e.segId])) {
                        intersections.push_back({*prev, e.segId});
                    }
                }
                
                auto next_it = next(it);
                if (next_it != active.end()) {
                    if (intersect(segments[e.segId], segments[*next_it])) {
                        intersections.push_back({e.segId, *next_it});
                    }
                }
            } else { // 结束事件
                auto it = active.find(e.segId);
                
                // 移除后检查邻居是否相交
                if (it != active.begin() && next(it) != active.end()) {
                    auto prev_it = prev(it);
                    auto next_it = next(it);
                    if (intersect(segments[*prev_it], segments[*next_it])) {
                        intersections.push_back({*prev_it, *next_it});
                    }
                }
                
                active.erase(it);
            }
        }
        
        return intersections;
    }
};

int main() {
    SweepLine sweepLine;
    
    // 添加测试线段
    sweepLine.addSegment(Point(0, 0), Point(4, 4));
    sweepLine.addSegment(Point(0, 4), Point(4, 0));
    sweepLine.addSegment(Point(1, 1), Point(3, 3));
    sweepLine.addSegment(Point(2, 0), Point(2, 5));
    
    vector<pair<int, int>> intersections = sweepLine.findIntersections();
    
    cout << "找到 " << intersections.size() << " 个相交:" << endl;
    for (auto& p : intersections) {
        cout << "线段 " << p.first << " 和 " << p.second << " 相交" << endl;
    }
    
    return 0;
}

扫描线在平衡二叉搜索树中按y坐标顺序维护活跃线段。当线段开始/结束时,我们检查其在活跃集合中的邻居的相交情况。

矩形并集面积

使用坐标压缩和扫描线计算矩形并集覆盖的总面积。

矩形并集面积计算

#include <iostream>
#include <vector>
#include <algorithm>
#include <map>
using namespace std;

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

struct Event {
    int x, y1, y2, type; // type: +1表示左边, -1表示右边
    
    bool operator<(const Event& other) const {
        if (x != other.x) return x < other.x;
        return type > other.type; // 先处理+1事件
    }
};

class SegmentTree {
private:
    vector<int> tree, lazy;
    vector<int> coords;
    int n;
    
    void push(int node, int start, int end) {
        if (lazy[node] != 0) {
            tree[node] = (lazy[node] > 0) ? coords[end + 1] - coords[start] : 0;
            if (start != end) {
                lazy[2 * node] += lazy[node];
                lazy[2 * node + 1] += lazy[node];
            }
            lazy[node] = 0;
        }
    }
    
    void update(int node, int start, int end, int l, int r, int val) {
        push(node, start, end);
        if (start > r || end < l) return;
        
        if (start >= l && end <= r) {
            lazy[node] += val;
            push(node, start, end);
            return;
        }
        
        int mid = (start + end) / 2;
        update(2 * node, start, mid, l, r, val);
        update(2 * node + 1, mid + 1, end, l, r, val);
        
        push(2 * node, start, mid);
        push(2 * node + 1, mid + 1, end);
        tree[node] = tree[2 * node] + tree[2 * node + 1];
    }
    
public:
    SegmentTree(vector<int>& coordinates) : coords(coordinates) {
        n = coords.size() - 1;
        tree.resize(4 * n);
        lazy.resize(4 * n);
    }
    
    void update(int l, int r, int val) {
        if (l <= r) update(1, 0, n - 1, l, r, val);
    }
    
    int query() {
        push(1, 0, n - 1);
        return tree[1];
    }
};

long long rectangleUnionArea(vector<Rectangle>& rectangles) {
    vector<Event> events;
    vector<int> yCoords;
    
    // 创建事件并收集y坐标
    for (const Rectangle& rect : rectangles) {
        events.push_back({rect.x1, rect.y1, rect.y2 - 1, 1});
        events.push_back({rect.x2, rect.y1, rect.y2 - 1, -1});
        yCoords.push_back(rect.y1);
        yCoords.push_back(rect.y2);
    }
    
    sort(events.begin(), events.end());
    sort(yCoords.begin(), yCoords.end());
    yCoords.erase(unique(yCoords.begin(), yCoords.end()), yCoords.end());
    
    // 坐标压缩映射
    map<int, int> yMap;
    for (int i = 0; i < yCoords.size(); i++) {
        yMap[yCoords[i]] = i;
    }
    
    SegmentTree segTree(yCoords);
    long long totalArea = 0;
    int prevX = events[0].x;
    
    for (const Event& event : events) {
        // 从上一个x到当前x添加面积
        totalArea += (long long)(event.x - prevX) * segTree.query();
        
        // 更新线段树
        int y1Idx = yMap[event.y1];
        int y2Idx = yMap[event.y2 + 1] - 1;
        segTree.update(y1Idx, y2Idx, event.type);
        
        prevX = event.x;
    }
    
    return totalArea;
}

int main() {
    vector<Rectangle> rectangles = {
        Rectangle(0, 0, 3, 3),
        Rectangle(1, 1, 4, 4),
        Rectangle(2, 2, 5, 5),
        Rectangle(6, 0, 8, 2)
    };
    
    long long area = rectangleUnionArea(rectangles);
    cout << "总并集面积:" << area << endl;
    
    // 验证单个面积
    cout << "单个矩形面积:" << endl;
    for (int i = 0; i < rectangles.size(); i++) {
        Rectangle& r = rectangles[i];
        int area = (r.x2 - r.x1) * (r.y2 - r.y1);
        cout << "矩形 " << i << ": " << area << endl;
    }
    
    return 0;
}

我们使用坐标压缩将y坐标映射到索引,然后维护线段树来跟踪在扫描x坐标时哪些y区间被覆盖。