POJ 3304 Segment 直线与线段相交 + 枚举

若存在这样一条直线,过投影相交区域作直线的垂线,该垂线必定与每条线段相交,问题转化为问是否存在一条线和所有线段相交;

若存在一条直线与所有线段相机相交,将该线旋转,平移,直到不能再动为止,此时该直线必定经过这些线段的某两个端点;

所以枚举任意两个端点即可。

 

需要注意的是,当枚举的两个端点很近的时候,即sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2))<eps时,那么这两个点是可以认为重合的,由于如果把这两点的连线作为向量,向量的模会很小,就可能导致无论怎么做叉积结果都为0从而认为各个点都是和这两个点共线的。因此,在枚举两个端点的时候要避免这种情况,同时,避免枚举这种情况之后也一定不会丢解。

 

View Code
#include<stdio.h>
#include<string.h>
#include<math.h>
#define eps 10e-8
struct point
{
    double x, y;
};
struct segment
{
    point s, t;
};
segment s[101];
int n;

double ff(double x)//平方
{
    return x*x;
}
bool dis(point a, point b)//判断2个点是不是同一个点,本题很近的点就算是同一个点
{
    double ret = sqrt(ff(a.x - b.x )+ff(a.y - b.y));
    if(-eps < ret && ret < eps)return 0;
    return 1;
}
double cross(point o, point a, point b)//叉积
{
    return (a.x - o.x)*(b.y - o.y)- (a.y - o.y)*(b.x - o.x);
}
bool judge(point a, point b)//直线的2个点位a,b,判断这条直线与n条线段是否相交,都相交返回1,否则返回0.
{
    if(!dis(a, b))return 0;
    int i;
    for(i=1;i<=n;i++)
        if(cross(a, b, s[i].s)*cross(a, b, s[i].t) > eps)return 0;
    return 1;
}
int main()
{
    int cas,i ,j ;
    scanf("%d",&cas);
    while(cas--)
    {
        scanf("%d",&n);
        for(i=1;i<=n;i++)
            scanf("%lf%lf%lf%lf", &s[i].s.x, &s[i].s.y, &s[i].t.x, &s[i].t.y);
        bool ok = 0;
        if(n==1)ok = 1;
        for(i=1;i<n;i++)//枚举线段的端点
            for(j=i+1;j<=n;j++)
                if(judge(s[i].s, s[j].s)||judge(s[i].s, s[j].t)||judge(s[i].t, s[j].s)||judge(s[i].t, s[j].t)){ok=1;break;}
        puts(ok ? "Yes!":"No!");
    }
    return 0;
}

 

 

posted @ 2012-08-15 19:47  To be an ACMan  Views(212)  Comments(0)    收藏  举报