TZOJ3348: 线段相交Ⅲ(规范、非规范、线段交点)

本文讲述了如何使用C++编程实现线段相交的判断,涉及规范相交和非规范相交的概念,以及利用向量法和叉积来求解交点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

TZOJ3348: 线段相交Ⅲ

题目传送门

描述

线段相交有两种情形:一种是“规范相交”,另一种是“非规范相交”。规范相交是指两条线段恰有唯一一个不是端点的公共点。即如果一条线段的端点在另一条线段上则不视为相交。如果两条线段有部分重合,也不视为相交。而非规范相交则把以上两种情况都视为相交。如下图所示:
在这里插入图片描述
规范相交认为a,b两种情况都是不相交的,而非规范相交认为a,b两种情况都是相交的。

本题要求判断两条线段是否相交。如果是规范相交则输出YES,并输出交点坐标,如果是非规范相交则只需输出YES,如果不相交则输出NO。

输入

输入有多组数据,T表示输入数据的组数。每组测试数据有两行第一行输入一条线段的两个端点的坐标,第二行输入另一个线段的两个端点的坐标。

输出

对于每组测试数据,输出一行。如果是规范相交则输出YES,并输出交点坐标(小数点后面保留3位),如果是非规范相交则只需输出YES,如果不相交则输出NO。

样例输入
4
0 0 1 1
0 1 1 0
0 0 2 2
2 2 3 3
0 0 2 2
1.5 1.5 3 3
0 0 1 1
2 2 3 3
样例输出
YES (0.500,0.500)
YES
YES
NO
解题思路

先判断规范的线段相交,再判断非规范的线段相交,如果相交则求线段交点。对于如何判断线段相交的细节原理可以参考线段相交。对于线段交点可以参考线段交点1线段交点2线段交点3。在本代码中我使用的是矢量法求解线段交点。

假设有两条线段,线段1的起点为P1(x1, y1),终点为Q1(x2, y2),线段2的起点为P2(x3, y3),终点为Q2(x4, y4)。
首先,计算线段1的向量表示为V1 = Q1 - P1,线段2的向量表示为V2 = Q2 - P2。
然后,计算线段1的参数方程为P1 + t1 * V1,线段2的参数方程为P2 + t2 * V2。
接下来,解方程 P1 + t1 * V1 = P2 + t2 * V2,求解出 t1 和 t2。
最后,将 t1 带入线段1的参数方程,即可得到交点的坐标。

具体怎么解呢?

首先,我们将P1 + t1 * V1 = P2 + t2 * V2展开:
x1 + t1 * (x2 - x1) = x3 + t2 * (x4 - x3)
y1 + t1 * (y2 - y1) = y3 + t2 * (y4 - y3)
然后,将上述两个方程进行整理:
(x1 - x3) + t1 * (x2 - x1) = t2 * (x4 - x3)
(y1 - y3) + t1 * (y2 - y1) = t2 * (y4 - y3)
接下来,我们可以通过消元的方法解出 t1 的值。
首先,将第一个方程乘以 (y4 - y3),将第二个方程乘以 (x4 - x3):
(y4 - y3) * ((x1 - x3) + t1 * (x2 - x1)) = t2 * (x4 - x3) * (y4 - y3)
(x4 - x3) * ((y1 - y3) + t1 * (y2 - y1)) = t2 * (x4 - x3) * (y4 - y3)
然后,展开并整理这两个方程:
((x1 - x3) * (y4 - y3)) + t1 * ((x2 - x1) * (y4 - y3)) = t2 * ((x4 - x3) * (y4 - y3))
((y1 - y3) * (x4 - x3)) + t1 * ((y2 - y1) * (x4 - x3)) = t2 * ((x4 - x3) * (y4 - y3))
接着,将第一个方程减去第二个方程,得到:
((x1 - x3) * (y4 - y3) - (y1 - y3) * (x4 - x3)) + t1 * ((x2 - x1) * (y4 - y3) - (y2 - y1) * (x4 - x3)) = 0
最后,将 t1 的项移至一侧,得到:
t1 = ((x3 - x4) * (y1 - y3) + (y4 - y3) * (x1 - x3)) / ((y4 - y3) * (x1 - x2) - (x4 - x3) * (y1 - y2))

PS:以上交点的求法有坑,精度不足,过不了某些题目比如这题

所以还是用以下方法吧:
这里也用到叉积的原理。假设交点为p0(x0,y0)。则有:

(p1-p0)X(p2-p0)=0

(p3-p0)X(p2-p0)=0

展开后即是

(y1-y2)x0+(x2-x1)y0+x1y2-x2y1=0

(y3-y4)x0+(x4-x3)y0+x3y4-x4y3=0

将x0,y0作为变量求解二元一次方程组。

假设有二元一次方程组

a1x+b1y+c1=0;

a2x+b2y+c2=0

那么

x=(c1b2-c2b1)/(a2b1-a1b2);

y=(a2c1-a1c2)/(a1b2-a2b1);

因为此处两直线不会平行,所以分母不会为0。

info getPoint(info a, info b, info c, info d) {//之前矢量推导出来的交点坐标居然精度没这个高!
    double a1 = b.y - a.y;
    double b1 = a.x - b.x;
    double c1 = b.x * a.y - a.x * b.y;
    double a2 = d.y - c.y;
    double b2 = c.x - d.x;
    double c2 = d.x * c.y - c.x * d.y;
    double x = (b1 * c2 - b2 * c1) / (a1 * b2 - a2 * b1);
    double y = (c1 * a2 - c2 * a1) / (a1 * b2 - a2 * b1);
    return {x, y};
}
代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define IOS ios::sync_with_stdio(0),cin.tie(0)
struct info {
    double x, y;
};
//判断点是否在线段上
bool onseg(info a, info b, info c) {
    if (b.x <= max(a.x, c.x) && b.x >= min(a.x, c.x) &&
        b.y <= max(a.y, c.y) && b.y >= min(a.y, c.y)) return true;
    return false;
}
//叉积判断点的位置
double xmult(info a, info b, info c) {
    return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
}
//判断线段相交
ll segcross(info a, info b, info c, info d) {
    double k1 = xmult(a, b, c);//a,b
    double k2 = xmult(a, b, d);
    double k3 = xmult(c, d, a);//c,d
    double k4 = xmult(c, d, b);
    //规范相交
    if(k1 * k2 < 0 && k3 * k4 < 0) return 1;
    //非规范相交
    if (k1 == 0 && onseg(a, c, b)) return 2;//a,b
    if (k2 == 0 && onseg(a, d, b)) return 2;
    if (k3 == 0 && onseg(c, a, d)) return 2;//c,d
    if (k4 == 0 && onseg(c, b, d)) return 2;
    return 0;
}
//矢量法获取两直线交点
info getPoint(info a, info b, info c, info d) {
    double x1 = a.x, y1 = a.y;
    double x2 = b.x, y2 = b.y;
    double x3 = c.x, y3 = c.y;
    double x4 = d.x, y4 = d.y;
    double t1 = ((x3-x4)*(y1-y3) + (y4-y3)*(x1-x3)) / ((y4-y3)*(x1-x2) - (x4-x3)*(y1-y2));
    double x = x1 + t1 * (x2 - x1);
    double y = y1 + t1 * (y2 - y1);
    return {x, y};
}
void solve() {
    vector<info> p(4);
    for (int i = 0; i < 4; ++i) {
        double x, y; cin >> x >> y;
        p[i] = {x, y};
    }
    ll ck = segcross(p[0], p[1], p[2], p[3]);
    if (ck) {
        if(ck == 1) {
            info point = getPoint(p[0], p[1], p[2], p[3]);
            cout << "YES (";
            cout << fixed << setprecision(3) << point.x << "," << point.y << ")" << endl;
        } else if(ck == 2) {
            cout << "YES" << endl;
        }
    } else {
        cout << "NO" << endl;
    }
}
int main() {
    IOS;
    ll t; cin >> t;
//    ll t = 1;
    while (t--) solve();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值