这几天闲来无事去学习了一下计算几何,发现其实不(sang)是(xin)太(bing)难(kuang)😛
今天就重点介绍一下简单的叉积及其简单的运用(毕竟做为蒟蒻,难的搞不来啊)node
其实所谓计算几何,就是用计算机去解决不一样维度上的几何问题,而咱们要作的,就是设计一套算法,让计算机去分析和解决一系列的几何问题。算法
通常来讲,计算几何都涉及到了小数坐标,这时开一个含两个double类型的结构体就比较稳健,我通常的定义以下:数组
struct node//定义一个点 { double x,y; }a[11000];
聪明绝顶的你确定已经想到了,线段就是包含两个点的一个结构体,因此我通常的定义以下函数
struct line//定义一条线 { node p1,p2; } list[11000];
终于到今天的主角—叉积了!😝
那么什么是叉积呢?叉积实际上是两个矢量模的乘积再乘夹角正弦,通过推导能够发现两个向量\(A(x1,y1),B(x2,y2)\)的叉积为:学习
先丢出代码:spa
double multi(node p1,node p2,node p0)//计算叉积,p1,p2,p0都为点 { double x1,y1,x2,y2; x1=p1.x-p0.x; y1=p1.y-p0.y; x2=p2.x-p0.x; y2=p2.y-p0.y; return x1*y2-x2*y1; }
这就是传说中的叉积计算了!是否是很短呢,那就让咱们来理解一下这个multi函数吧。设计
因为叉积是向量间的运算,你们都知道向量能够用末坐标减去首坐标获得,那么其实multi函数计算的就是向量A(x1,y1)与向量B(x2,y2)的叉积。code
对于multi函数的意义,首先multi的正负是有特殊含义的:以p0为参考点,若是multi大于0,则p2在p1的逆时针方向,反正,若是multi小于0,则p2在p1的顺时针方向,特殊的,当multi等于0,p一、p二、p0三点共线。
blog
其次,multi的值也是有含义的!经过后面的学习你就会发现,multi的绝对值就是以p0、p一、p2三点构成的三角形的面积的两倍!利用这个规律,能够完成不少与计算几何有关的题目。string
题目描述
【题意】
有n条线段(编号为1~n),按1~n的顺序放在二维坐标系上(就是先放1号,再放2号……),
要求输出最上面的那些线段的编号(就是没有其余线段压在它上面的那些线段)
【输入格式】
第一行第一个数n( 1 <= n <= 10000)表示这组数据有n条线段。
下来n行,每行两个坐标,表示第i条线段的两个端点。
【输出格式】
一行。输出最上面的线段的编号(从小到大)。相邻两个编号用空格隔开,最后一个编号没有空格。
【样例1输入】
5
1 1 4 2
2 3 3 1
1 -2.0 8 4
1 4 8 2
3 3 6 -2.0
【样例1输出】
2 4 5
【样例2输入】
3
0 0 1 1
1 0 2 1
2 0 3 1
【样例2输出】
1 2 3
这道题很明显要让咱们判断任意两条线段是否相交,这就要用到刚刚讲到的叉积的第一种用法了!
很明显,相交有两种状况,相交且穿过和相交但不穿过(以下图)
咱们首先先讨论第一种状况,如何判断两条直线相交且穿过?
如上图,先假设两条直线为L1和L2,每条直线的两个端点分别为p1和p2,那么若是以
L1.p1为一个参考点,若是L1.p2在L2.p1与L2.p2中间;再以L2.p1为参考点,若是L2.p2在L1.p1与L1.p2中间,那么就能够判断L1与L2相交且穿过(以下图)
这种状况很是好理解吧,下一种状况就更加简单了,仅需讨论哪三个点在一条“直线”上(毕竟不穿过,因此必有三点共线),而后判断一下
不穿过的那个点是否在一条“线段”上(以下图)
具体代码以下
double check(line l1,line l2)//判断是否相交 { if(multi(l2.p1,l1.p2,l1.p1)*multi(l1.p2,l2.p2,l1.p1)>0)//判断以L1.p1为基准,L1.p2是否在L2.p1与L2.p2中间 if(multi(l1.p1,l2.p2,l2.p1)*multi(l2.p2,l1.p2,l2.p1)>0)//判断以L2.p1为基准,L2.p2是否在L1.p1与L2.p2中间 return true;//大部分状况下,能判断两直线相交 if(multi(l1.p1,l1.p2,l2.p1)==0)if(mymin(l1.p1.x,l1.p2.x)<=l2.p1.x)if(mymax(l1.p1.x,l1.p2.x)>=l2.p1.x)if(mymin(l1.p1.y,l1.p2.y)<=l2.p1.y)if(mymax(l1.p1.y,l1.p2.y)>=l2.p1.y)return true; if(multi(l1.p1,l1.p2,l2.p2)==0)if(mymin(l1.p1.x,l1.p2.x)<=l2.p2.x)if(mymax(l1.p1.x,l1.p2.x)>=l2.p2.x)if(mymin(l1.p1.y,l1.p2.y)<=l2.p2.y)if(mymax(l1.p1.y,l1.p2.y)>=l2.p2.y)return true; if(multi(l2.p1,l2.p2,l1.p1)==0)if(mymin(l2.p1.x,l2.p2.x)<=l1.p1.x)if(mymax(l2.p1.x,l2.p2.x)>=l1.p1.x)if(mymin(l2.p1.y,l2.p2.y)<=l1.p1.y)if(mymax(l2.p1.y,l2.p2.y)>=l1.p1.y)return true; if(multi(l2.p1,l2.p2,l1.p2)==0)if(mymin(l2.p1.x,l2.p2.x)<=l1.p2.x)if(mymax(l2.p1.x,l2.p2.x)>=l1.p2.x)if(mymin(l2.p1.y,l2.p2.y)<=l1.p2.y)if(mymax(l2.p1.y,l2.p2.y)>=l1.p2.y)return true; //判断两条直线相交且一条直线端点在另外一条直线上 return false; }
在以前的版本里,我在判断两条直线相交且一条直线端点在另外一条直线上的状况中忽略了两条直线互相一条与x轴垂直,一条与y轴垂直,且某一条延伸出去刚好交于另外一条直线交点的状况,因而在一次比赛中WA了十多发QwQ,下面放上错误代码以做为警示:
double check(line l1,line l2)//判断是否相交 { if(multi(l2.p1,l1.p2,l1.p1)*multi(l1.p2,l2.p2,l1.p1)>0)//判断以L1.p1为基准,L1.p2是否在L2.p1与L2.p2中间 if(multi(l1.p1,l2.p2,l2.p1)*multi(l2.p2,l1.p2,l2.p1)>0)//判断以L2.p1为基准,L2.p2是否在L1.p1与L2.p2中间 return true;//大部分状况下,能判断两直线相交 if(multi(l2.p1,l1.p1,l1.p2)==0)if(mymin(l1.p1.x,l1.p2.x<=l2.p1.x)if(mymax(l1.p1.x,l1.p2.x)>=l2.p1.x)return true; if(multi(l2.p2,l1.p1,l1.p2)==0)if(mymin(l1.p1.x,l1.p2.x<=l2.p2.x)if(mymax(l1.p1.x,l1.p2.x)>=l2.p2.x)return true; if(multi(l1.p1,l2.p1,l2.p2)==0)if(mymin(l2.p1.x,l2.p2.x<=l1.p1.x)if(mymax(l2.p1.x,l2.p2.x)>=l1.p1.x)return true; if(multi(l1.p2,l2.p1,l2.p2)==0)if(mymin(l2.p1.x,l2.p2.x<=l1.p2.x)if(mymax(l2.p1.x,l2.p2.x)>=l1.p2.x)return true; //判断两条直线相交且一条直线端点在另外一条直线上 return false; }
主程序就很简单了,一条一条边读入,读入第i条边时判断他与前i-1条边是否相交,若相交,这以前的边j被盖住,判断数组bo【j】=1。最后统计bo【i】==0的边就好啦
附上完整代码:
#include<cmath> #include<cstdlib> #include<cstdio> #include<cstring> using namespace std; struct node//定义一个点 { double x,y; }; struct line//定义一条线 { node p1,p2; } list[11000]; int a[11000]={0}; bool bo[11000]={0}; int i,j,k,m,n,o,p,js,jl,jk,len; //================================ double mymax(double x,double y)//求max { if(x>y)return x; else return y; } double mymin(double x,double y)//求min { if(x<y)return x; else return y; } //================================= double multi(node p1,node p2,node p0)//计算叉积:并且值为三点构成三角形的面积的两倍 { double x1,y1,x2,y2; x1=p1.x-p0.x; y1=p1.y-p0.y; x2=p2.x-p0.x; y2=p2.y-p0.y; return x1*y2-x2*y1; } double check(line l1,line l2)//判断是否相交 { if(multi(l2.p1,l1.p2,l1.p1)*multi(l1.p2,l2.p2,l1.p1)>0)//判断以L1.p1为基准,L1.p2是否在L2.p1与L2.p2中间 if(multi(l1.p1,l2.p2,l2.p1)*multi(l2.p2,l1.p2,l2.p1)>0)//判断以L2.p1为基准,L2.p2是否在L1.p1与L2.p2中间 return true;//大部分状况下,能判断两直线相交 if(multi(l1.p1,l1.p2,l2.p1)==0)if(mymin(l1.p1.x,l1.p2.x)<=l2.p1.x)if(mymax(l1.p1.x,l1.p2.x)>=l2.p1.x)if(mymin(l1.p1.y,l1.p2.y)<=l2.p1.y)if(mymax(l1.p1.y,l1.p2.y)>=l2.p1.y)return true; if(multi(l1.p1,l1.p2,l2.p2)==0)if(mymin(l1.p1.x,l1.p2.x)<=l2.p2.x)if(mymax(l1.p1.x,l1.p2.x)>=l2.p2.x)if(mymin(l1.p1.y,l1.p2.y)<=l2.p2.y)if(mymax(l1.p1.y,l1.p2.y)>=l2.p2.y)return true; if(multi(l2.p1,l2.p2,l1.p1)==0)if(mymin(l2.p1.x,l2.p2.x)<=l1.p1.x)if(mymax(l2.p1.x,l2.p2.x)>=l1.p1.x)if(mymin(l2.p1.y,l2.p2.y)<=l1.p1.y)if(mymax(l2.p1.y,l2.p2.y)>=l1.p1.y)return true; if(multi(l2.p1,l2.p2,l1.p2)==0)if(mymin(l2.p1.x,l2.p2.x)<=l1.p2.x)if(mymax(l2.p1.x,l2.p2.x)>=l1.p2.x)if(mymin(l2.p1.y,l2.p2.y)<=l1.p2.y)if(mymax(l2.p1.y,l2.p2.y)>=l1.p2.y)return true; //判断两条直线相交且一条直线端点在另外一条直线上 return false; } //================================== int main() { scanf("%d",&n); for(int i=1;i<=n;i++)scanf("%lf%lf%lf%lf",&list[i].p1.x,&list[i].p1.y,&list[i].p2.x,&list[i].p2.y); for(int i=1;i<n;i++) for(int j=i+1;j<=n;j++) { if(check(list[i],list[j]))//判断相交 { bo[i]=1; break; } } len=0; for(int i=1;i<=n;i++)if(bo[i]==0)a[++len]=i; for(int i=1;i<len;i++)printf("%d ",a[i]); printf("%d\n",a[len]); return 0; }
题目描述
【题意】
在一个平面坐标系上随意画一条有n个点的封闭折线(按画线的顺序给出点的坐标),保证封闭折线的任意两条边都不相交。最后要计算这条路线包围的面积。
【输入格式】
第一行整数 n (3 <= n <= 1000),表示有n个点。
下来n行,每行两个整数x(横坐标)和y(纵坐标),表示点坐标(-10000<x,y<=10000)。
【输出格式】
一行一个实数,即封闭折线所包围的面积(保留4位小数)。
【样例1输入】
4
2 1
5 1
5 5
2 5
【样例1输出】
12.0000
【样例2输入】
5
2 1
5 1
3 2
5 3
2 3
【样例2输出】
4.0000
这道题对比上一道题就简单不少了,这道题要用到叉积的第二种用法——计算面积!对于任意一个多边形,只要以一号点p1为参考点,枚举任意两个相邻的点pi和pi-1(i>=3),带符号计算p1和pi、pi-1所构成的三角形的面积(multi(pi,pi-1,p1)/2),累加取绝对值就是答案了。
至于为何要带符号运算,请看我用下面这幅图演示
对于这个多边形,首先先加上multi(p3,p2,p1)/2的值(因为p3在p2的逆时针方向,因此为正)即为加上了红色部分
而后再加上multi(p4,p3,p1)/2的值(因为p4在p3的顺时针方向,因此为负)即为减去了绿色部分
而后再加上multi(p5,p4,p1)/2的值(因为p5在p4的逆时针方向,因此为正)即为加上了黄色部分,就是答案了
最后附上代码
#include<cmath> #include<cstdio> #include<cstdlib> #include<cstring> #include<algorithm> using namespace std; struct node { double x,y; }a[11000]; int i,j,k,m,n,o,p,js,jl; double ans; double multi(node p1,node p2, node p0) { double x1=p1.x-p0.x; double y1=p1.y-p0.y; double x2=p2.x-p0.x; double y2=p2.y-p0.y; return(x1*y2-x2*y1); } int main() { scanf("%d",&n); for(int i=1;i<=n;i++)scanf("%lf%lf",&a[i].x,&a[i].y); for(int i=2;i<=n;i++) { ans=ans+multi(a[i],a[i-1],a[1]); } ans=ans/2.0; if(ans<0)ans=-ans; printf("%0.4lf",ans); return 0; }
经过上面的学习和例题,是否是已经初步了解了计算几何的一点点精(皮)髓(毛)呢,但愿你能有所收获,在计算几何的路上越走越远吧(蒟蒻的我多是走不下去了) 最后Orz各位大佬,写的很差请各位神犇提出建议和意见,感谢你的阅读:neckbeard: