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

题意

给出 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
#include
#include

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

你可能感兴趣的:(计算几何)