当前位置:首页 > 比赛题解 > 2024年蓝桥杯国赛B组蚂蚁开会(洛谷P10907):线段相交问题的解法

2024年蓝桥杯国赛B组蚂蚁开会(洛谷P10907):线段相交问题的解法

1天前比赛题解47

截图未命名.jpg 2024年蓝桥杯国赛B组蚂蚁开会(洛谷P10907):线段相交问题的解法 线段相交 蓝桥杯国赛 蓝桥杯 C++ 第1张

一、问题背景

题目模拟了蚂蚁在平面上爬行的轨迹(线段),需要计算所有蚂蚁轨迹线段的整数交点数量。这是典型的计算几何问题,考察线段相交判断和特殊情况的处理能力。

二、完整代码解析(含详细注释)

struct Point {
    int x, y;
    Point(int x=0, int y=0) : x(x), y(y) {}
    bool operator<(const Point& other) const {
        return x < other.x || (x == other.x && y < other.y);
    }
};

struct Segment {
    Point p1, p2;
    Segment(Point p1, Point p2) : p1(p1), p2(p2) {}
};

// 计算叉积
long long cross(const Point& a, const Point& b, const Point& c) {
    return (long long)(b.x - a.x) * (c.y - a.y) - (long long)(b.y - a.y) * (c.x - a.x);
}

// 判断点是否在线段上
bool onSegment(const Point& p, const Segment& s) {
    if (cross(s.p1, s.p2, p) != 0) return false;
    return p.x >= min(s.p1.x, s.p2.x) && p.x <= max(s.p1.x, s.p2.x) &&
           p.y >= min(s.p1.y, s.p2.y) && p.y <= max(s.p1.y, s.p2.y);
}

// 计算两条线段的整数交点
bool getIntegerIntersection(const Segment& s1, const Segment& s2, Point& res) {
    Point A = s1.p1, B = s1.p2;
    Point C = s2.p1, D = s2.p2;
    
    long long a1 = B.y - A.y;
    long long b1 = A.x - B.x;
    long long c1 = a1 * A.x + b1 * A.y;
    
    long long a2 = D.y - C.y;
    long long b2 = C.x - D.x;
    long long c2 = a2 * C.x + b2 * C.y;
    
    long long det = a1 * b2 - a2 * b1;
    
    if (det == 0) { // 平行或共线
        // 处理共线情况
        set<Point> points;
        if (onSegment(C, s1)) points.insert(C);
        if (onSegment(D, s1)) points.insert(D);
        if (onSegment(A, s2)) points.insert(A);
        if (onSegment(B, s2)) points.insert(B);
        
        if (points.size() > 0) {
            res = *points.begin();
            return true;
        }
        return false;
    }
    
    // 计算交点
    long long x = b2 * c1 - b1 * c2;
    long long y = a1 * c2 - a2 * c1;
    
    // 检查是否为整数点
    if (x % det != 0 || y % det != 0) return false;
    
    res.x = x / det;
    res.y = y / det;
    
    // 检查交点是否在线段上
    if (res.x < min(A.x, B.x) || res.x > max(A.x, B.x) ||
        res.x < min(C.x, D.x) || res.x > max(C.x, D.x) ||
        res.y < min(A.y, B.y) || res.y > max(A.y, B.y) ||
        res.y < min(C.y, D.y) || res.y > max(C.y, D.y)) {
        return false;
    }
    
    return true;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    int n;
    cin >> n;
    vector<Segment> segments;
    
    for (int i = 0; i < n; ++i) {
        int x1, y1, x2, y2;
        cin >> x1 >> y1 >> x2 >> y2;
        segments.emplace_back(Point(x1, y1), Point(x2, y2));
    }
    
    set<Point> intersections;
    
    for (int i = 0; i < n; ++i) {
        for (int j = i + 1; j < n; ++j) {
            Point p;
            if (getIntegerIntersection(segments[i], segments[j], p)) {
                intersections.insert(p);
            }
        }
    }
    
    cout << intersections.size() << endl;
    
    return 0;
}

三、核心算法解析

  1. 叉积计算:通过向量叉积判断点线关系、线段相交情况

  2. 共线处理:特殊处理平行和共线情况下的交点判断

  3. 整数交点验证:确保交点坐标为整数且位于线段范围内

  4. 去重处理:使用set自动处理重复交点

四、常见问题解答

Q:为什么使用long long存储叉积? A:防止整数乘法溢出导致判断错误

Q:如何处理多条线段共点的情况? A:set会自动去重,确保每个交点只计数一次

Q:算法能否处理垂直线段? A:可以,算法对任意方向的线段都有效


原创内容 转载请注明出处

分享给朋友:

发表评论

访客

看不清,换一张

◎欢迎参与讨论,请在这里发表您的看法和观点。