线段相交是计算几何的基础知识,有必要熟练掌握。
关于叉积:
int mul(point p0,point p1,point p2){
return (p1.x-p0.x)*(p2.y-p0.y)-(p1.y-p0.y)*(p2.x-p0.x);
}
如果叉积结果大于0,表示p2-p0在p0-p1的逆时针方向(图中例子结果为4);如果叉积结果小于0,表示p2-p0在p0-p1的顺时针方向;如果叉积结果是0,那么3点共线。【这很好理解:向量积|c|=|a×b|=|a| |b|sin<a,b>。】
判断线段相交:
bool Cross(point a,point b,point c,point d){//判断ab 与cd是否相交
double re1,re2,re3,re4;
re1=crossmulti(c,d,a);
re2=crossmulti(c,d,b);
re3=crossmulti(a,b,c);
re4=crossmulti(a,b,d);
if(re1*re2<0&&re3*re4<0)return 1;
else if(re1==0&&OnSegment(c,d,a))return 1; //四种在同一条线上的结果
else if(re2==0&&OnSegment(c,d,b))return 1;
else if(re3==0&&OnSegment(a,b,c))return 1;
else if(re4==0&&OnSegment(a,b,d))return 1;
return 0;
}
“if(re1*re2<0&&re3*re4<0)return 1; ”:
“else if(re1==0&&OnSegment(c,d,a))return 1; ”:
剩余三种是三种类似。
bool OnSegment(point a,point b,point c){ //a,b,c共线时使用
if(c.x>=min(a.x,b.x)&&c.x<=max(a.x,b.x)&&c.y>=min(a.y,b.y)&&c.y<=max(a.y,b.y))return 1;
return 0;
}
则保证了第三个点在前两个点所构成的矩形之内:
所以,在re1==0成立的基础上,OnSegment(c,d,a)排除了第三个点a在c,d的延长线的情况,一定在线上。
线段相交的简单应用:
hdu 1086 You can Solve a Geometry Problem too
题目:http://acm.hdu.edu.cn/showproblem.php?pid=1086
算是模板题吧。
#include <iostream>
#include <cstdio>
#include<algorithm>
using namespace std;
const int maxn=105;
struct point{
double x,y;
};
struct node{
point a,b;
}edge[maxn];
double crossmulti(point a,point b,point c){
return (b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x);
}
bool OnSegment(point a,point b,point c){
if(c.x>=min(a.x,b.x)&&c.x<=max(a.x,b.x)&&c.y>=min(a.y,b.y)&&c.y<=max(a.y,b.y))return 1;
return 0;
}
bool Cross(point a,point b,point c,point d){
double re1,re2,re3,re4;
re1=crossmulti(c,d,a);
re2=crossmulti(c,d,b);
re3=crossmulti(a,b,c);
re4=crossmulti(a,b,d);
if(re1*re2<0&&re3*re4<0) return 1;
else if(re1==0&&OnSegment(c,d,a)) return 1;
else if(re2==0&&OnSegment(c,d,b)) return 1;
else if(re3==0&&OnSegment(a,b,c)) return 1;
else if(re4==0&&OnSegment(a,b,d)) return 1;
return 0;
}
int main()
{
//freopen("cin.txt","r",stdin);
int n;
point p1,p2;
while(cin>>n&&n){
int ans=0;
for(int i=0;i<n;i++){
scanf("%lf%lf%lf%lf",&p1.x,&p1.y,&p2.x,&p2.y);
edge[i].a=p1;
edge[i].b=p2;
for(int j=0;j<i;j++){ //单向
if(Cross(edge[i].a,edge[i].b,edge[j].a,edge[j].b)) ans++;
}
}
printf("%d\n",ans);
}
return 0;
}
fzu 1015 土地划分
题目: http://acm.fzu.edu.cn/problem.php?pid=1015
当前线段的终点就是下一个线段的起点。问,能把矩形划成几份?
分析:画一条边构成的土地数量就是f(1)=2,两条线段就是 f(2)=4,通过画图可以发现,每增加一条边,部分其他边会分割它,由此增加L+1条线段(L表示分割它的边数,也就是交点数),一条线段将空间分成两 份,增加一份,所以f(n)=f(n-1)+Ln+1=f(n-2)+L(n- 1)+1+Ln+1=……=f(1)+L2+1+……+Ln+1=2+L2+L3+……+Ln+n-1=n+1+∑Li. 结果与边数和总的交点数相关。注意本题中端点接触不算严格相交。
#include <iostream>
#include <cstdio>
using namespace std;
const int maxn=60;
struct point{
int x,y;
};
struct vect{
point s,e;
}v[maxn];
int multi(point p1,point p2,point p0){
return (p1.x-p0.x)*(p2.y-p0.y)-(p2.x-p0.x)*(p1.y-p0.y);
}
bool OnSegment(point p1,point p2,point p0){
if(p0.x<=max(p1.x,p2.x)&&p0.x>=min(p1.x,p2.x)&&p0.y<=max
(p1.y,p2.y)&&p0.y>=min(p1.y,p2.y))
return 1;
return 0;
}
bool cross(vect l1,vect l2){
int r1,r2,r3,r4;
r1=multi(l2.s,l2.e,l1.s);
r2=multi(l2.s,l2.e,l1.e);
r3=multi(l1.s,l1.e,l2.s);
r4=multi(l1.s,l1.e,l2.e);
if(r1*r2<0&&r3*r4<0) return 1;
//else if(r1==0&&OnSegment(l2.s,l2.e,l1.s)) return 1;
//else if(r2==0&&OnSegment(l2.s,l2.e,l1.e)) return 1;
//else if(r3==0&&OnSegment(l1.s,l1.e,l2.s)) return 1;
//else if(r4==0&&OnSegment(l1.s,l1.e,l2.e)) return 1;
else return 0;
}
int main()
{
//freopen("cin.txt","r",stdin);
int w,h,l;
while(~scanf("%d%d",&w,&h)&&(w+h)){
scanf("%d",&l);
point p1,p2;
int cnt=0;
scanf("%d%d",&p1.x,&p1.y);
for(int i=0;i<l;i++){
scanf("%d%d",&p2.x,&p2.y);
v[cnt].s=p1;
v[cnt++].e=p2;
p1=p2;
}
int L=0;
for(int i=0;i<cnt;i++){
for(int j=i+1;j<cnt;j++){
if(cross(v[i],v[j])){
L++;
}
}
}
printf("%d\n",L+l+1);
}
return 0;
}
还有一些问题,不但要求判断是否线段相交,还需要求出其交点坐标。
图中P是AB和DC的交点。
poj 1269 Intersecting Lines(线段的二维关系·交点坐标)
题目: http://poj.org/problem?id=1269
大意:判断线段的平面关系,平行,相交,重叠。注意,有一种未接触相交的空间关系,所以不能直接套用跨立排斥算法。
#include <iostream>
#include <cstdio>
using namespace std;
struct point{
int x,y;
}p[4];
struct node{
double x,y;
}po;
int multi(point p1,point p2,point p0){
return (p1.x-p0.x)*(p2.y-p0.y)-(p2.x-p0.x)*(p1.y-p0.y);
}
int judge(){
int r1,r2,r3,r4;
r1=multi(p[0],p[2],p[3]);
r2=multi(p[1],p[2],p[3]);
if(r1==0&&r2==0) return 1; //重合
if((p[0].x-p[1].x)*(p[2].y-p[3].y)==(p[0].y-p[1].y)*(p[2].x-p[3].x)) return 3; //平行
return 2; //相交
}
int main()
{
//freopen("cin.txt","r",stdin);
int n;
cin>>n;
printf("INTERSECTING LINES OUTPUT\n");
while(n--){
for(int i=0;i<4;i++) scanf("%d%d",&p[i].x,&p[i].y);
int res=judge();
if(res==1) puts("LINE");
else if(res==3) puts("NONE");
else {
int q1,q2;
q1=multi(p[3],p[0],p[1])*p[2].x-multi(p[2],p[0],p[1])*p[3].x;
q2=multi(p[3],p[0],p[1])-multi(p[2],p[0],p[1]);
po.x=q1*1.0/q2;
q1=multi(p[3],p[0],p[1])*p[2].y-multi(p[2],p[0],p[1])*p[3].y;
po.y=q1*1.0/q2;
printf("POINT %.2lf %.2lf\n",po.x,po.y);
}
}
printf("END OF OUTPUT\n");
return 0;
}