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

一、问题背景
题目模拟了蚂蚁在平面上爬行的轨迹(线段),需要计算所有蚂蚁轨迹线段的整数交点数量。这是典型的计算几何问题,考察线段相交判断和特殊情况的处理能力。
二、完整代码解析(含详细注释)
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;
}三、核心算法解析
叉积计算:通过向量叉积判断点线关系、线段相交情况
共线处理:特殊处理平行和共线情况下的交点判断
整数交点验证:确保交点坐标为整数且位于线段范围内
去重处理:使用set自动处理重复交点
四、常见问题解答
Q:为什么使用long long存储叉积? A:防止整数乘法溢出导致判断错误
Q:如何处理多条线段共点的情况? A:set会自动去重,确保每个交点只计数一次
Q:算法能否处理垂直线段? A:可以,算法对任意方向的线段都有效
原创内容 转载请注明出处
