POJ 2653 Pick-up sticks【判断线段相交】

POJ 2653 Pick-up sticks

题目大意:有n根木条,一根一根的往一个坐标系上丢(给出木条两点的坐标),问最后不被覆盖的木条有哪些,即丢的木条如果和前面丢的木条交叉的话,就会覆盖前面那根木条

方法:快速排斥实验和跨立实验

程序中可用的模板

1.【判断double符号】

(有时需要判断一个Double类型的正负或零,但由于精度问题,需要引入一个小量eps=1e-8,用它来判断是否为0)

2.【定义点、线段的结构体】

(点point、线段segment)

结构体中定义了以自身名字命名的函数,这样方便赋值!

重载了运算符+,-,*,/,==,det(叉乘)dot(点乘)

点可以看成向量,向量的模长(norm)

3.【判断两线段是否相交】

Judge函数:用到了 #快速排斥试验# 和 #跨立试验#(下面有提到)

 

 

快速排斥实验和跨立实验

矢量

     如果一条线段的端点是有次序之分的话,那么这种线段就称为有向线段,如果有向线段p1p2的起点p1在坐标的原点,则可以把它称为矢量 p2

  矢量的加减

     设二维矢量 P = (x1, y1), Q = (x2, y2),则 P + Q = (x1 +x2, y1 + y2), P - Q = (x1 - x2, y1 - y2),且有 P + Q = Q + P,P - Q = -(Q - P)

  矢量叉积

     设矢量 P = (x1, y1), Q = (x2, y2),则 P * Q = x1 * y2- x2 * y1; 其结果是一个由 (0, 0), P, Q, P + Q所组成的平行四边形的带符号的面积,P * Q = -(Q *P), P * (- Q) = -(P * Q)

     叉积的一个非常重要的性质是可以通过它的符号来判断两矢量相互之间的顺逆时针关系:

            P * Q > 0,则 P Q 的顺时针方向

            P * Q < 0, P Q 的逆时针方向

            P * Q = 0,则 P Q 共线,但不确定 P, Q的方向是否相同

折线段的拐向判断

     如图,假设有折线段 p0p1p2,要确定 p1p2相对于 p0p1 是向左拐还是向右拐,可以通过计算(p2 - p0)*(p1 -p0)的符号来确定折线的拐向(点 p2 - p0实际上就是向量 p2,但这里要注意线段和矢量的区别)

判断点是否在线段上

     设点 Q = (x, y), P1 = (x1, y1), P2 = (x2, y2),若 (Q - P1)*(P2 - P1) = 0 min(x1, x2)<= x <= max(x1, x2) && min(y1, y2) <= y <= max(y1, y2),则点 Q在线段 P1P2

判断两线段是否相交

1)快速排斥试验

     设以线段 P1P2为对角线的矩形为 R,设以线段 Q1Q2为对角线的矩形为 T,若 RT不相交,则两线段不可能相交

     假设 P1 = (x1, y1), P2 = (x2, y2), Q1 = (x3, y3), Q2 = (x4,y4),设矩形 R x 坐标的最小边界为 minRX = min(x1, x2),以此类推,将矩形表示为 R = (minRX,minRY, maxRX, maxRY)的形式,若两矩形相交,则相交的部分构成了一个新的矩形 F,如图所示,我们可以知道 F minFX =max(minRX, minTX), minFY = max(minRY, minTY), maxFX = min(maxRX, maxTX), maxFY= min(maxRY, maxTX),得到 F的各个值之后,只要判断矩形 F是否成立就知道 R T 到底有没有相交了,若 minFX > maxFX minFY >maxFy F无法构成,RT不相交,否则 RT相交

2)跨立试验

      P1P2跨立 Q1Q2,则 P1,P2分别在 Q1Q2所在直线的两端,则有 (P1 - Q1)*(Q2 - Q1) * (Q2 - Q1)*(P2 - Q1) > 0,当 (P1 - Q1)*(Q2 - Q1) = 0时,说明 (P1 - Q1) (Q2 - Q1)共线,但由于已经经过快速排斥试验,所以 Q1必为 P1P2 Q1Q2 的交点,依然满足线段相交的条件,则跨立试验可改为:

      (P1 - Q1)*(Q2 - Q1) * (Q2 - Q1)*(P2 - Q1) >= 0,则 P1P2 跨立 Q1Q2,当 Q1Q2也跨立 P1P2的时候,则 P1P2 相交(必须是相互跨立!!!)

     (注意式子中被隔开的 *代表两个叉积的值的相乘,而另外的两个 *则代表计算矢量叉积)

 

//POJ2653
#include<stdio.h>
#include<math.h>
#include<string.h>
#include<math.h>
#include <iostream>
using namespace std;

const double eps=1e-8;
int sgn(double x)
{
    if(fabs(x)<eps) return 0;
    if(x>0) return 1;
    return -1;
}

struct point
{
    double x,y;
    //下面这个函数貌似是为了方便写入赋值,参见第82行
    point(){}
    point(double _x,double _y)
    {
        x=_x;y=_y;
    }
    //下面是一些运算符重载
    point operator +(const point &b)const
    {
        return point(x+b.x,y+b.y);
    }
    point operator -(const point &b)const
    {
        return point(x-b.x,y-b.y);
    }
    point operator *(const double &b)const //倍数需乘在后面
    {
        return point(x*b,y*b);
    }
    point operator /(const double &b) const
    {
        return point(x/b,y/b);
    }
    bool operator ==(const point &b) const
    {
        return sgn(x-b.x)==0 && sgn(y-b.y)==0;
    }
    double det(const point &b)const //用……表示叉乘,用时前面加.即可
    {
        return x*b.y-y*b.x;
    }
    double dot(const point &b) const//点乘
    {
        return x*b.x+y*b.y;
    }
    double norm()
    {
        return sqrt(x*x+y*y);
    }
};


struct segment
{
    point s,e;
    segment(){}
    segment(point _s,point _e)
    {
        s=_s;e=_e;
    }
};

int judge(segment p,segment q)
{
    if(min(p.s.x,p.e.x)>max(q.s.x,q.e.x)||
       max(p.s.x,p.e.x)<min(q.s.x,q.e.x)||
       min(p.s.y,p.e.y)>max(q.s.y,q.e.y)||
       max(p.s.y,p.e.y)<min(q.s.y,q.e.y)) return 0; //快速排斥试验


    if(sgn((q.s-p.s).det(p.e-p.s))*sgn((q.e-p.s).det(p.e-p.s))<=0&&
       sgn((p.s-q.s).det(q.e-q.s))*sgn((p.e-q.s).det(q.e-q.s))<=0)
        return 1;
    else return 0;                                  //跨立试验(必须要相互跨立,故有两个<=0)
}

const int maxn=100005;
segment seg[maxn];
int flag[maxn];

int main()
{
    int n,i,j;
    double x1,y1,x2,y2;
    while(scanf("%d",&n)==1&&n)
    {
        for(i=0;i<n;i++)
        {
            scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
            seg[i]=segment(point(x1,y1),point(x2,y2));
        }
        memset(flag,1,sizeof(flag));
        for(i=0;i<n;i++)
            for(j=i+1;j<n;j++)
            if(judge(seg[i],seg[j]))
            {
                flag[i]=0;
                break;
            }
        printf("Top sticks:");
        int first=1;
        for(i=0;i<n;i++)
            if(flag[i])
            if(first)
            {
                first=0;
                printf(" %d",i+1);
            }
            else printf(", %d",i+1);
        printf(".\n");
    }

    return 0;
}




  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值