线段相交Ⅲ

3348: 线段相交Ⅲ
时间限制(普通/Java):1000MS/3000MS 内存限制:64000KByte

描述

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

规范相交认为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

思路

计算几何模板题

两直线状态有非相交和相交,相交又可以分成规范相交和非规范相交

判断两直线是否相交,看每条线段两点是否在另一条线的两边

若有线段两段点在另一线段同一边,则不相交,否则两线相交
在相交的情况下,如果有一条线段的段点在另一线段上,则为非规范相交,否则为规范相交。

这里可以用向量来判断两线是否相交

按图建立三个向量,然后 b1a1向量 和 b1a2向量 分别与 b1b2向量叉乘,得到的结果如果为正数,则点在向量右边,为负数则点在向量左边,若为零,则点在向量所在直线上。

这里将四个端点依次作为向量起始点来判断,如果有出现叉积为0的情况,则点在该线段所在直线上,再用点乘再次判断即可点是否在另一条线段上

只要出现一个点在另一线段上,即可判断为非规范相交;

如果所有叉乘结果显示一线段两段点都在另一线段的两边,即可判断为规范相交

否则判断为不相交

如果判断结果为规范相交,则可计算出交点

计算方法有很多,这里 基于重心坐标法和平行四边形面积的性质 可以求坐标

有两条线段 AB 和 CD,它们的交点为 E

将AB和CD参数化

C' = C + t * (D - C)
A' = A + s * (B - A)

现在要找到 t 和 s 的值,使得 C' = A',即E点位置。

C + t * (D - C) = A + s * (B - A)

t的值表示了线段 CD 上的点 C' 在线段上的位置关系,而 s 的值表示了线段 AB 上的点 A' 在线段上的位置关系。

重心坐标法中可以用权重来表示这些位置关系。

现在考虑两个权重,一个用来表示 C' 相对于线段 CD 的位置,另一个用来表示 A' 相对于线段 AB 的位置。我们将这两个权重分别记为 w1 和 w2。

根据平行四边形面积的性质,我们可以使用叉积来表示面积。这样可以得到以下关系:

w1 = |det(C - A, B - A)|
w2 = |det(D - A, B - A)|

现在可以使用这些权重来计算交点 E 的坐标。E 的 x 坐标可以通过以下公式计算:

E.x = (w1 * D.x + w2 * C.x) / (w1 + w2)
E.y = (w1 * D.y + w2 * C.y) / (w1 + w2)

AC代码

#include <bits/stdc++.h>
using namespace std;
const double PRECISION = 1e-8;
class Point
{
public:
    double x, y;
};
int dblcmp(double d)
{
    return (fabs(d) < PRECISION) ? 0:(d>0 ? 1:-1);
}
double dotdet(double x1, double y1, double x2, double y2)
{
    return x1*x2 + y1*y2;
}
double det(double x1, double y1, double x2, double y2)
{
    return x1*y2 - x2*y1;
}
int cross(const Point &a, const Point &c, const Point &d)
{
    return dblcmp( det(a.x-c.x, a.y-c.y, d.x-c.x, d.y-c.y) );
}
bool between(const Point &a, const Point &c, const Point &d)
{
    return dblcmp( dotdet(c.x-a.x, c.y-a.y, d.x-a.x, d.y-a.y) ) != 1;
}
int segIntersect(const Point &a, const Point &b, const Point &c, const Point &d)
{
    int a_cd = cross(a,c,d);
    if(a_cd == 0 && between(a,c,d))return 2;
    int b_cd = cross(b,c,d);
    if(b_cd == 0 && between(b,c,d))return 2;
    int c_ab = cross(c,a,b);
    if(c_ab == 0 && between(c,a,b))return 2;
    int d_ab = cross(d,a,b);
    if(d_ab == 0 && between(d,a,b))return 2;
    if ((a_cd ^ b_cd) == -2 && (c_ab ^ d_ab) == -2) // 所有 结果为-1 和 1
        return 1;
    return 0;
}
void GetPoint(const Point &a, const Point &b, const Point &c, const Point &d, Point &e)
{
    double sc, sd;
    sc = fabs( det(b.x-a.x, b.y-a.y, c.x-a.x, c.y-a.y) );
    sd = fabs( det(b.x-a.x, b.y-a.y, d.x-a.x, d.y-a.y) );
    e.x = (sc * d.x + sd * c.x) / (sc + sd);
    e.y = (sc * d.y + sd * c.y) / (sc + sd);
}

void solve()
{
    Point a{},b{},c{},d{},ans{};
    cin>>a.x>>a.y>>b.x>>b.y;
    cin>>c.x>>c.y>>d.x>>d.y;
    int temp=segIntersect(a,b,c,d);
    if(!temp)cout<<"NO"<<endl;
    else if(temp==2)cout<<"YES"<<endl;
    else
    {
        GetPoint(a,b,c,d,ans);
        cout<<fixed<<setprecision(3)<<"YES ("<<ans.x<<","<<ans.y<<")"<<endl;
    }
}
signed main()
{
    int _=1;
    cin>>_;
    while(_--)
    {
        solve();
    }
}
posted @ 2023-08-13 01:29  Minza  阅读(27)  评论(0编辑  收藏  举报