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;
}