计算几何-线段

24 篇文章 0 订阅
5 篇文章 0 订阅

首先是直线相交,这个简单,就是看斜率,斜率不同则相交。

重点分析线段与线段相交,给你两组坐标P1(x1,y1),P2(X2,y2),Q1(x3,y3),Q2(x4,y4),判断P1P2与Q1Q2是否相交:

首先可以很快排除下面四种情况:

对于第①种情况满足条件:max(Q1.x,Q2.x)<min(P1.x,P2.x),直接返回不相交。以此类推,不相交的情况为:

if(max(P1.x,P2.x)<min(Q1.x,Q2.x)|| 
   min(P1.x,P2.x)>max(Q1.x,Q2.x)||
   min(P1.y,P2.y)>max(Q1.y,Q2.y)||
   max(P1.y,P2.y)<min(Q1.y,Q2.y)    
)  return false; 

对于下面两种当以P1P2为对角线形成的矩形和以Q1Q2为对角线形成的矩形相交时,一种若是都在同侧,肯定不相交,判断条件是利用叉积的概念,我们知道三角形P1Q2P2和

三角形P1Q1P2的点的顺序是相同的,那么他们的叉积的乘积肯定大于0。

同样如果两条线段相交,那么肯定在不同侧,那么他们的叉积的乘积一定<0。记得要满足P1,P2在线段Q1Q2的两侧,同时也要满足Q1,Q2在线段P1P2的两侧。

所以这个时候的判断条件为:

#define exp 1e-6
double dx=multi(P1,P2,Q1);
double dy=multi(P1,P2,Q2);
double zx=multi(Q1,Q2,P1);
double zy=multi(Q1,Q2,P2);
return dx*dy<=exp&&zx*zy<=exp;

完整的判断条件见下面的例题。

例题:POJ 1410(线段与矩阵相交),意思简单,给你两个坐标和矩阵的对角坐标,判断这条线段是否与矩阵相交。

#include<iostream>
#include<cmath>
using namespace std;
#define exp 1e-8
struct Point
{   double x,y;
    friend istream& operator>>(istream &cin,Point &P)
    {   cin>>P.x>>P.y;
        return cin;
    }
};
Point P1,P2,T[10];
double multi(Point p0,Point p1,Point p2) //计算叉积        
{   return (p1.x-p0.x)*(p2.y-p0.y)-(p2.x-p0.x)*(p1.y-p0.y);      
}  
bool Inter(Point T1,Point T2)
{   if(max(P1.x,P2.x)<min(T1.x,T2.x)||  //快速排斥  
       min(P1.x,P2.x)>max(T1.x,T2.x)||
       min(P1.y,P2.y)>max(T1.y,T2.y)||
       max(P1.y,P2.y)<min(T1.y,T2.y)    
    )  return false;                                                      
    double dx=multi(P1,P2,T1);
    double dy=multi(P1,P2,T2);
    double zx=multi(T1,T2,P1);
    double zy=multi(T1,T2,P2);
    return dx*dy<=exp&&zx*zy<=exp;
}
int main()
{   int Case;
    cin>>Case;
    while(Case--)
    {   cin>>P1>>P2>>T[0]>>T[2];
        T[1].x=T[0].x,T[1].y=T[2].y;
        T[3].x=T[2].x,T[3].y=T[0].y; 
        int flag=0;
        for(int i=1;i<=4;i++)
            if(Inter(T[i-1],T[i%4])) flag=1; 
        if(flag) cout<<'T'<<endl; 
        else
        {   if(min(P1.x,P2.x) > min(T[0].x,T[2].x)&& //判断线段是否在矩形内部
               max(P1.x,P2.x) < max(T[0].x,T[2].x)&&
               min(P1.y,P2.y) > min(T[0].y,T[2].y)&&
               max(P1.y,P2.y) < max(T[0].y,T[2].y)  
            ) cout<<'T'<<endl;
            else cout<<'F'<<endl; 
        }      
    }    
    return 0;
}

例题2:NYOJ 83(迷宫寻宝),直接模举宝藏位置与各点和相邻点的中点的连线与每一道墙的交点的个数取最小值即可。

难点就是给定的点的顺序不确定,我怎么知道如图点(100,61)的相邻的点是(100,47)和(100,90)呢?

所以我们得将这些点进行排序。排序的依据不能是按照X或者Y的大小排序,所以可以将坐标原点移动到中心(50,50)的位置。如下图所示:

那么每个定点与中心的连线跟X轴就会成一定的角度,这个时候我们就可以根据角度来进行排序了。角度可以利用斜率然后反正切就是角度大小,具体见代码。

#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;
const int MAX=100;
#define Inf 0x7fffffff
#define exp 1e-6
struct Point
{   double x,y;
    Point(){}
    Point(double n,double m):x(n),y(m){}
    friend istream& operator>>(istream &cin,Point &P)
    {   cin>>P.x>>P.y;
        return cin;
    }
    bool operator<(const Point &P) const 
    {   return atan2(y-50,x-50)<atan2(P.y-50,P.x-50);
    }
};
struct Line
{   Point a,b;
};
double multi(Point P1,Point P2,Point P)
{   return (P1.x-P.x)*(P2.y-P.y)-(P2.x-P.x)*(P1.y-P.y);
}
bool Inter(Point P1,Point P2,Point T1,Point T2)
{   if(max(P1.x,P2.x)<min(T1.x,T2.x)|| 
       min(P1.x,P2.x)>max(T1.x,T2.x)||
       min(P1.y,P2.y)>max(T1.y,T2.y)||
       max(P1.y,P2.y)<min(T1.y,T2.y)    
    )  return false;                                                      
    double dx=multi(P1,P2,T1);
    double dy=multi(P1,P2,T2);
    double zx=multi(T1,T2,P1);
    double zy=multi(T1,T2,P2);
    return dx*dy<=exp&&zx*zy<=exp;
}
Point T[MAX]; 
Line L[MAX];
int main()
{   
    int n,Case;
    cin>>Case;
    while(Case--) 
    {   int ans=Inf;
        cin>>n;
        int sum=0;
        T[sum++]=Point(0,0);
        T[sum++]=Point(0,100);
        T[sum++]=Point(100,100);
        T[sum++]=Point(100,0);
        for(int i=0;i<n;i++)
        {   cin>>L[i].a>>L[i].b;
            T[sum++]=L[i].a;
            T[sum++]=L[i].b;
        } 
        Point Pos;
        cin>>Pos;
        sort(T,T+sum);
        for(int i=1;i<=sum;i++)
        {   Point Mid=Point((T[i-1].x+T[i%sum].x)/2.0,(T[i-1].y+T[i%sum].y)/2.0);
            int num=0;
            for(int j=0;j<n;j++)
                if(Inter(Mid,Pos,L[j].a,L[j].b)) num++;
            if(ans>num) ans=num;    
        }
        cout<<ans+1<<endl;
    }
    return 0;
}

例题3:POJ 3304(线段与直线相交),意思是给你几条线段,然后从这些线段的端点上任取两个顶点构成的直线如果能够与所有的线段相交,输出Yes!,否则输出No!
反正这个计算几何就像大数题一样,烦的死的是精度和细节。这个题我是WA了n次。

注意点就是:如果输入数据只有一组,直接输出Yes!,还有就是OUTPUT里面最后一句话要特别注意。如果两点之间的距离<10^-8,那么视为相等不做处理。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
const int MAX=210;
#define exp 1e-8
struct Point
{   double x,y;
};
struct Line
{   Point a,b;
};
Line L[MAX];
double Dis(Point p1,Point p2)
{   return sqrt((p1.x-p2.x)*(p1.x-p2.x)+(p1.y-p2.y)*(p1.y-p2.y));
}
bool Inter(Point P1,Point P2,Point Q1,Point Q2) //直线与线段相交模板:点(P1,P2)是否与过点(Q1,Q2)的直线相交
{   Point t1,t2,t3;
    t1.x=P1.x-Q1.x,t1.y=P1.y-Q1.y;
    t2.x=Q2.x-Q1.x,t2.y=Q2.y-Q1.y;
    t3.x=P2.x-Q1.x,t3.y=P2.y-Q1.y;
    if((t1.x*t2.y-t1.y*t2.x)*(t2.x*t3.y-t2.y*t3.x)>=0) return true;
    return false;
}
int main()
{   int n,Case;
    scanf("%d",&Case);
    Line temp;
    while(Case--)
    {   scanf("%d",&n);
        for(int i=0;i<n;i++)
        {   scanf("%lf%lf%lf%lf",&L[i].a.x,&L[i].a.y,&L[i].b.x,&L[i].b.y);
            L[n+i].b=L[i].a,L[n+i].a=L[i].b;
        }
        int flag=0;    
        for(int i=0;!flag&&i<2*n;i++)
            for(int j=0;j<2*n;j++)
            {   temp.a=L[i].a;
                temp.b=L[j].b;
                if(Dis(temp.a,temp.b)<exp) continue;
                int first=1;
                for(int k=0;k<n;k++)
                    if(!Inter(L[k].a,L[k].b,temp.a,temp.b))
                    {   first=0; 
                       break;
                    }
                if(first) flag=1; 
            }
        if(n==1) puts("Yes!");
        else puts(flag?"Yes!":"No!");    
    }  
    return 0;
}

题4:POJ 1556(门),这道题在NYOJ 227(有趣的问题)出现过,题目意思是:

在一个长宽均为10,入口出口分别为(0,5)、(10,5)的房间里,有几堵墙,每堵墙上有两个缺口,求入口到出口的最短路经。

方法是将每扇门的上下起点和入口和出口作为无向图的顶点,如果两个点之间相互能够直达则形成了无向图的一条边。两点间的距离作为这条边的权值,然后利用Dijkstra算法求最短路即可。
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值