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=1127AC代码
#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; }
相关文章推荐
- pku 1556 The Doors 计算几何 之 叉积判断线段是否相交
- ccsu1359 木棒相交 (叉积线段判交,并查集判断是否属于同一个集合)
- 使用叉积判断两条线段是否相交
- 【计算几何】 POJ 1127 Jack Straws 判断线段是否相交
- 判断两条线段是否相交(叉积)
- 【计算几何】 POJ 1127 Jack Straws 判断线段是否相交
- hdu 1558 Segment set 并查集 叉积判断线段是否相交
- Jack Straws(判断线段是否相交 + 并查集)
- POJ3304_Segments_叉积::判断直线与线段是否相交
- 判断两线段是否相交-考研真题
- 判断平面上任意两条线段是否相交-Python实现
- 线段 ——2D(如何判断线段是否相交)
- 判断两条线段是否相交
- 1410 Intersection 判断线段和矩形是否相交 转换为判断和矩形四条边是否相交以及线段是否在矩形内部 包含端点
- 判断两条线段是否相交_模版
- 如何确定两个线段是否相交;叉积的利用;
- 计算几何基础——矢量和叉积 && 叉积、线段相交判断、凸包(转载)
- HDU1086(判断两线段是否相交)
- zoj 1648 Circuit Board (判断线段是否相交)
- POJ 2653 Pick-up sticks 枚举判断线段是否相交