您的位置:首页 > 其它

POJ1127_Jack Straws_叉积::判断两线段是否相交

2017-06-21 21:30 274 查看

题意

给出 n 条线段端点的坐标,然后给出若干组询问。每组询问包含两个数字,输出这两个数字代表的线段是否联通。线段从 1 到 n 编号。通过联通的线段间接连在一起的线段,认为这两条线段也是联通的。

思路

判断两线段是否相交,首先求出两线段所在直线的交点,然后看这个交点是否在两条线段上。如果在,则两条线段相交。

可以利用坐标求两直线的方程然后进行运算,但运用向量的内积和外积更简单。

内积与外积

内积 = x1 * y1 + x2 * y2 , 外积 = x1 * y 2 - x2 * y1 。

外积的公式可以借助行列式来记忆,对于三维坐标也适用。

判断点是否在线段上

1.利用外积判断点q是否在p1 - p2线段所在的直线上

线段所在直线的方向向量可以表示成 p1 - p2 。

点在直线上,则有 (p1 - q) X (p2 - q) = 0

2.利用内积进一步判断点是否在线段上

点在直线上的基础上,进一步判断点是否落在线段内,即线段的两端点之间。

直线上的点在线段上,则有 (p1 - q) * (p2 - q) <= 0

利用外积求两直线(p1 - p2, q1 - q2)的交点

设一个变量 t,则直线 p1 - p2 上的点可以表示成 p1 + t (p1 - p2) 。又该点也在直线 q1 - q2 上,所有有 (q2 - q1) X (p1 + t(p1 - p2) - p1) = 0 。解出 t 的表达式后代入 p1 + t(p1 - p2) 即得到了交点的向量即坐标:

p1 + (p2 - p1) * ((q2 - q1).det(q1 - p1) / (q2 - q1).det(p2 - p1));

特殊情况

如果两条线段所在直线的方向向量平行,这两条线段依然有可能相交。对于这种情况,若只需要判断线段的端点即可。

若两条线段中,至少一条线段的至少一个端点在另一条线段上,则这两条线段相交。

算法过程

1.首先判断两条线段所在直线的方向向量是否平行,若平行,则按照特殊情况处理

2.对于不平行的情况,首先利用公式求出两线段所在直线的交点,然后判断这个交点是否在两条线段上。如果在,则两条线段相交。

3.最后,利用floyd算法求出线段联通关系的图即可。

注意

1.几何问题一定要注意特殊情况的处理

具体分析可见《挑战程序设计竞赛 253 页》

2.计算误差问题

1.设置 eps,一般为 1e-10 。

a < 0 == a < -eps

a <= 0 == a < eps

a = 0 == abs(a) < eps

2.注意平方运算会使误差快速变大

3.考虑误差的加法运算(见AC代码)

题目链接

http://poj.org/problem?id=1127

AC代码

#include<cstdio>
#include<iostream>
#include<cstring>

using namespace std;

const int maxn = 15;
const double eps = 1e-10;

//取绝对值函数
double Abs(double x)
{
return (x > 0) ? x : -x;
}

//考虑误差的加法运算
double add (double x, double y)
{
if(Abs(x + y) < eps * (Abs(x) + Abs(y))) return 0;
return x + y;
}

//二维向量结构体
struct P
{
double x, y;

P(){}

P(double a, double b)
:x(a), y(b){}

P operator + (P p)
{
return P(add(x, p.x), add(y, p.y));
}

P operator -(P p)
{
return P(add(x, -p.x), add(y, -p.y));
}

P operator *(double d)
{
return P(x * d, y * d);
}

//内积
double dot(P p)
{
return add(x * p.x, y * p.y);
}

//外积
double det(P p)
{
return add(x * p.y, -p.x * y);
}
};

//判断点是否在直线上
bool on_seg(P p1, P p2, P q)
{
return (p1 - q).det(p2 - q) == 0 && (p1 - q).dot(p2 - q) <= 0;
}

//求两直线的交点,注意是直线
P inter(P p1, P p2, P q1, P q2)
{
return p1 + (p2 - p1) * ((q2 - q1).det(q1 - p1) / (q2 - q1).det(p2 - p1));
}

int n;
P p[maxn], q[maxn]; //保存一条线段的两个端点
bool G[maxn][maxn]; //线段之间是否联通的图

int main()
{
while(1)
{
memset(G, false, sizeof G);

cin >> n;
if(n == 0) break;

for(int i= 0; i< n; i++)
cin >> p[i].x >> p[i].y >> q[i].x >> q[i].y;

for(int i= 0; i< n; i++)
for(int j= 0; j< n; j++)
{
//两线段所在直线平行
//检查两线段的端点是否在另一条线段上
if((p[i] - q[i]).det(p[j] - q[j]) == 0)
{
G[i][j] = G[j][i] =
on_seg(p[i], q[i], p[j]) || on_seg(p[i], q[i], q[j])
|| on_seg(p[j], q[j], p[i]) || on_seg(p[j], q[j], q[i]);

}

//两线段所在直线不平行
//求两直线的交点,然后检查交点是否在两条线段上
else
{
P r = inter(p[i], q[i], p[j], q[j]);
G[i][j] = G[j][i] = on_seg(p[i], q[i], r) && on_seg(p[j], q[j], r);
}
}

//floyd算法求线段之间的联通关系
for(int k= 0; k< n; k++)
for(int i= 0; i< n; i++)
for(int j= 0; j< n; j++)
G[i][j] |= G[i][k] && G[k][j];

while(1)
{
int x, y;
cin >> x >> y;
if(x == 0 && y == 0) break;

x --, y --;
if(G[x][y]) cout << "CONNECTED\n";
else cout << "NOT CONNECTED\n";
}
}

return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: