现在的位置: 首页 > 综合 > 正文

ZOJ 1648 Circuit Board【跨立实验判断线段相交】

2013年07月21日 ⁄ 综合 ⁄ 共 6647字 ⁄ 字号 评论关闭

计算几何(判断线段是否相交问题)

题目链接:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=1648

CSUST 2012年暑假8月组队后个人训练赛第11场,题目A:http://acm.hust.edu.cn:8080/judge/contest/view.action?cid=11851#problem/A

另外附上一个很牛逼的博客的判断线段相交问题:http://www.cnblogs.com/g0feng/archive/2012/05/18/2508293.html

Circuit Board


Time Limit: 2 Seconds      Memory Limit: 65536 KB


On the circuit board, there are lots of circuit paths. We know the basic constrain is that no two path cross each other, for otherwise the board will be burned.

Now given a circuit diagram, your task is to lookup if there are some crossed paths. If not find, print "ok!", otherwise "burned!" in one line.

A circuit path is defined as a line segment on a plane with two endpoints p1(x1,y1) and p2(x2,y2).

You may assume that no two paths will cross each other at any of their endpoints.

Input

The input consists of several test cases. For each case, the first line contains an integer n(<=2000), the number of paths, then followed by n lines each with four float numbers x1, y1, x2, y2.

Output

If there are two paths crossing each other, output "burned!" in one line; otherwise output "ok!" in one line.


Sample Input

1
0 0 1 1

2
0 0 1 1
0 1 1 0

Sample Output

ok!
burned!

题目大意:在一个电板上以坐标的形式给你N条线段,我们都知道电路中的线路是不能相交的,所以一旦遇到相交的情况就输出burned!否则输出ok!

相关转化:说白了就是给你N条线段,让你判断给出的线段是否相交。用跨立实验直接判断即可,一旦相交就跳出循环,输出burned!否则输出ok!

相关算法计算几何,用跨立实验判断两线段不相交。

关于判断两线段是否相交(思想来自大一上学期寒假学长给的计算几何资料):

我们分两步确定两条线段是否相交:

(1)快速排斥试验

设以线段 P1P2 为对角线的矩形为R, 设以线段 Q1Q2 为对角线的矩形为T,如果RT不相交,显然两线段不会相交。

(PS:我自己貌似从来没有用过这个,都是直接用的下面的跨立实验)

(2)跨立试验

(PS:下面的公式中*代表点积,×代表叉积

如果两线段相交,则两线段必然相互跨立对方。

P1P2跨立Q1Q2 ,则矢量 ( P1 - Q1 ) ( P2 - Q1 )位于矢量( Q2 - Q1 ) 的两侧,

( P1 - Q1 ) × ( Q2 - Q1 ) * ( P2 - Q1 ) × ( Q2 - Q1 ) < 0

上式可改写成( P1 - Q1 ) × ( Q2 - Q1 ) * ( Q2 - Q1 ) × ( P2 - Q1 ) > 0

当 ( P1 - Q1 ) × ( Q2 - Q1 ) = 0 时,说明 ( P1 - Q1 ) 和 ( Q2 - Q1 )共线,但是因为已经通过快速排斥试验,所以 P1 一定在线段 Q1Q2上;

同理,( Q2 - Q1 ) ×(P2 - Q1 ) = 0 说明 P2 一定在线段 Q1Q2上。

所以判断P1P2跨立Q1Q2的依据是:( P1 - Q1 ) × ( Q2 - Q1 ) * ( Q2 - Q1 ) × ( P2 - Q1 ) >
 0

同理判断Q1Q2跨立P1P2的依据是:( Q1 - P1 ) × ( P2 - P1 ) * ( P2 - P1 ) × ( Q2 - P1 ) >
 0
。 


在相同的原理下,对此算法的具体的实现细节可能会与此有所不同,除了这种过程外,大家也可以参考《算法导论》上的实现。

判断线段和直线是否相交:

有了上面的基础,这个算法就很容易了。如果线段P1P2和直线Q1Q2相交,则P1P2跨立Q1Q2

即:( P1 - Q1 ) × ( Q2 - Q1 ) * ( Q2 - Q1 ) × ( P2 - Q1 ) > 0

上面是我看的学长给的资料,下面是我自己的解释:

有关这个公式:( P1 - Q1 )×( Q2 - Q1 )*( Q2 - Q1 )×( P2 - Q1 ) > 0。(与上面的一样,只是我习惯了这个形式。。。)

看懂这个公式需要掌握的数学知识:向量的叉积和向量的点积。

向量的点积:

                 给你两个向量A(ax,by),B(bx,by).

                 向量A和B的点积公式:A*B=ax*bx+ay*by.

向量的叉积:

                给你两个向量A(ax,by) ,B(bx,by).

                向量的叉积公式:A×B=ax*by-ay*bx.

            具体看下面的行列式:

                               i        j       k

             A×B =     ax     ay     0  =  ax*by-ay*bx   = |A|*|B|*sin<A,B>    注意:叉积的结果仍然是个向量,A×B = -B×A

                               bx     by    0

叉积的几何意义:

                            
右手定则:
如上图所示,红色的向量表示V×U的结果。四指形成一个平面,大拇指与四指形成的平面垂直,四个指从V扫描到U大拇指的指向就是叉乘所得向量的方向。

继续跨立解释: 如果(P1-P2)跨立(Q1-Q2)那么向量(Q1-P1)×(Q1-Q2)后的向量方向与向量(Q1-Q2)×(Q1-P1)后的向量方向的夹角一定                   小于九十度 。那么他们的点积就 >0

 

注意:一般情况下,为了避免跨立不相交的局面,一定要判断线段P1P2跨立Q1Q2后再反过来判断Q1Q2跨立P1P2如果两者都跨立,才能断定他们相交。

以上只是个人小结,部分东西没有提及,有些东西也说的过于啰嗦,不对之处,还望各路大神批评指正。具体的这些理论知识还是找百度较好~~~~~~

 

下面是我自己写的一个判断两线段是否相交的模板:

P1P2横跨Q1Q2:

                               ( P1 - Q1 )×( Q2 - Q1 )*( Q2 - Q1 )×( P2 - Q1 ) > 0

                              temp={ (P1.x-Q1.x)*(Q2.y-Q1.y) - (P1.y-Q1.y)*(Q2.x-Q1.x) } * {(Q2.x-Q1.x)*(P2.y-Q1.y) - (Q2.y-Q1.y)*(P2.x-Q1.x)}>0


最好还是写成结构体模块化:

struct Point {
    double x,y;
    Point() {}
    Point(double _x, double _y) {
        x = _x; y = _y;
    }

    Point operator -(const Point &b) const {
        return Point(x-b.x, y-b.y);
    }
};

struct Line {
    Point a,b;
    Line() {}
    Line(Point _a, Point _b) {
        a = Point(_a.x, _a.y);
        b = Point(_b.x, _b.y);
    }
}line[maxn];

double Cross(Point a, Point b) { //叉积
    return a.x*b.y-a.y*b.x;
}

下面我就以直线的跨立实验详细说明下:

PS:直线可以无限延长,所以如果 L1 跨立了 L2,那么L2 必定跨立 L1, 那么 L1 与 L2 必然相交

          线段就要判断 L1 跨立 L2 并且 L2 跨立 L1 才能说明 二者相交。

         


如图所示: 直线 L2 跨立了直线 L1, L2与 L1 必然相交【但是如果它们是线段的话,是不相交的】

Cross(V1, V2) 代表 向量 V1和 V2的叉积

注意到 V1 偏向 V2   和 V2 偏向 V3 的方向是相同的,如果偏转的方向相同,那么说明他们的叉积必定同为正或者同为负,

当然上面的这个图自己比划一下就可以知道Cross(V1, V2) 是为正的

所以如果 Cross(V1,V2)*Cross(V2, V3) > 0 那么说明 L2 跨立 L1

下面我们再来看Cross(v1, v2) = 0 的情况:

如果 L1 和 L2 是线段:v1 v2共线,那么必定是相交于端点。

同时从另外一个角度来讲:可以看成是直线 V1 和 V2共线的情况。





关于这道题目要注意的地方:题目中要求的是点的坐标是float型,开始我没注意,一直用的int,结果TLE(超时)一上午啊!!!

线段可以相交于端点。

 

2013-08-20重做模块化代码:

#include<stdio.h>
#include<math.h>
#include<algorithm>
#include<iostream>;
using namespace std;

const int maxn = 2000+10;
int n;

struct Point {
    double x,y;
    Point() {}
    Point(double _x, double _y) {
        x = _x; y = _y;
    }

    Point operator -(const Point &b) const {
        return Point(x-b.x, y-b.y);
    }
};

struct Line {
    Point a,b;
    Line() {}
    Line(Point _a, Point _b) {
        a = Point(_a.x, _a.y);
        b = Point(_b.x, _b.y);
    }
}line[maxn];

double Cross(Point a, Point b) { //叉积
    return a.x*b.y-a.y*b.x;
}

double esp = 1e-5;
int dcmp(double x) {
    if(fabs(x) < esp) return 0;
    else return x > 0 ? 1 : -1;
}
//线段相交判断两次跨立实验
bool isCross(Line L1, Line L2) // = 0可以相交于端点
{
    double tmp1 = Cross(L2.a-L1.a, L1.b-L1.a);
    double tmp2 = Cross(L1.b-L1.a, L2.b-L1.a);

    double tmp3 = Cross(L1.a-L2.a, L2.b-L2.a);
    double tmp4 = Cross(L2.b-L2.a, L1.b-L2.a);
    if(dcmp(tmp1*tmp2) >= 0 && dcmp(tmp3*tmp4) >= 0) return true;
    return false;
}

int main()
{
    while(scanf("%d", &n) != EOF) {
        Point a,b;
        for(int i = 0; i < n; i++) {
            scanf("%lf%lf%lf%lf", &a.x, &a.y, &b.x, &b.y);
            line[i] = Line(a,b);
        }

        int flag = 1;
        for(int i = 1; i < n; i++)
        {
            for(int j = 0; j < i; j++)
            {
                if(isCross(line[i], line[j]))
                {
                    flag = 0; break;
                }
            }
        }
        if(flag) printf("ok!\n");
        else printf("burned!\n");
    }
    return 0;
}

//AC 244kb 20ms ZOJ1648 Circuit Board

代码一:

//AC 244kb 20ms ZOJ1648 Circuit Board
#include<stdio.h>
struct Line
{
    double x1;
    double y1;
    double x2;
    double y2;
}line[2010];
int main()
{
    int n;
    int i,j;
    while(scanf("%d",&n)!=EOF)
    {
        int ok=1;//判断是否有线段相交
        for(i=0;i<n;i++)
            scanf("%lf%lf%lf%lf",&line[i].x1,&line[i].y1,&line[i].x2,&line[i].y2);
		for(i=0;i<n-1;i++)
		{
			for(j=i+1;j<n;j++)
			{
				double s1=((line[i].x1-line[j].x1)*(line[j].y2-line[j].y1)-(line[i].y1-line[j].y1)*(line[j].x2-line[j].x1))
                          *((line[j].x2-line[j].x1)*(line[i].y2-line[j].y1)-(line[j].y2-line[j].y1)*(line[i].x2-line[j].x1));
				if(s1>0)//如果line[i]跨立line[j]下面继续判断line[j]是否跨立line[i]
				{
					double s2=((line[j].x1-line[i].x1)*(line[i].y2-line[i].y1)-(line[j].y1-line[i].y1)*(line[i].x2-line[i].x1))
                          *((line[i].x2-line[i].x1)*(line[j].y2-line[i].y1)-(line[i].y2-line[i].y1)*(line[j].x2-line[i].x1));
					if(s2>0)//如果二者均跨立,那么就不用进行下面的判断,直接跳出循环
					{
						ok=0; 
						break;
					}
				}
			}
			if(ok==0)  //注意一个break只能跳出一个for循环
				break;
		}
	    if(ok==1)
			printf("ok!\n");
		else
			printf("burned!\n");

    }
    return 0;
}

 

  

 

代码二:(想一步跳出循环用goto写的)

#include<stdio.h>
struct Line
{
    double x1;
    double y1;
    double x2;
    double y2;
}line[2010];
int main()
{
    int n;
    int i,j;
    while(scanf("%d",&n)!=EOF)
    {
        int ok=1;
        for(i=0;i<n;i++)
            scanf("%lf%lf%lf%lf",&line[i].x1,&line[i].y1,&line[i].x2,&line[i].y2);
		for(i=0;i<n-1;i++)
		{
			for(j=i+1;j<n;j++)
			{
				double s1=((line[i].x1-line[j].x1)*(line[j].y2-line[j].y1)-(line[i].y1-line[j].y1)*(line[j].x2-line[j].x1))
                          *((line[j].x2-line[j].x1)*(line[i].y2-line[j].y1)-(line[j].y2-line[j].y1)*(line[i].x2-line[j].x1));
				if(s1>0)
				{
					double s2=((line[j].x1-line[i].x1)*(line[i].y2-line[i].y1)-(line[j].y1-line[i].y1)*(line[i].x2-line[i].x1))
                          *((line[i].x2-line[i].x1)*(line[j].y2-line[i].y1)-(line[i].y2-line[i].y1)*(line[j].x2-line[i].x1));
					if(s2>0)
					{
						ok=0;goto begin;
					}
				}
			}
		}
begin:
	    if(ok==1)
			printf("ok!\n");
		else
			printf("burned!\n");

    }
    return 0;
}

 

 

 

 


抱歉!评论已关闭.