凸包知识简要概括(构造凸包)
什么是凸包,简单来说就是一个凸多边形。
场景
现在有一些点,让你画一个包含所有点的圈,要求图形的周长最短,很明显,用这些点上的一部分点作为节点连城直线是最短的,而最后的图形就是一个凸多边形。
我们如何来找到凸多边形上的点,第一我们可以发现最边缘的点一定位于最后的凸包上,例如y值最小(最大),x值最小(最大)的点,我们可以利用这些点作为我们构造凸包的起点。
首先,对所有点进行排序,找到y值最小的点,如果存在多组y相同,取最左边的点。
找到起点之后,进行极角排序,就是数学上的比较直线的斜率值(k),显然, arctan(k) 就是到x轴的角度,起点与其他的点连线求出角度值,从小到大排序,排序出的第一个点很显然也在凸包上,因为不存在更右的点。
还可以用叉积的方法去判断是否在左在右。
1 double cross(ff a,ff b,ff c) 2 { 3 return (b.x - a.x)*(c.y-a.y)-(c.x - a.x)*(b.y - a.y); 4 } 5 bool cmp(ff a,ff b) 6 { 7 double m = cross(f[0],a,b); 8 if(m>0) 9 return 1; 10 if(m==0&&dis(f[0],a)-dis(f[0],b)<=0) 11 return 1; 12 return 0; 13 }//用叉积去判断顺序,如果m==0,说明两点在同一直线上,则距离近的排在前面
用排好的点一个个判断是不是凸包上的点,现在我们有两个数组,一个g数组存放凸包上的点,一个f数组放排好序的点,
刚开始时,g数组上有两个点,起点和排序后的第一个点。现在点是否属于凸包。
设置一个cnt值,指向g数组的尾节点。
如果下一个点于g[cnt]的连线相对g[cnt]和g[cnt-1]的连线偏左,该点加入g数组,cnt++;
如果偏右,cnt--,该点继续重复比较的步骤,直到找到相对偏左的点(该过程相当于就是删去s数组凸包内的点)
当最后一个点判断完成后,g数组就储存了从起点逆时针一圈凸包上的点
求凸包的周长
顺序枚举g数组上的点累加dis(g[i],g[i+1]),最后再加上dis(g[cnt],g[0])
完整代码
1 #include<iostream> 2 #include<cstdio> 3 #include<cmath> 4 #include<algorithm> 5 #include<cstring> 6 using namespace std; 7 struct ff 8 { 9 double x,y; 10 bool operator < (ff b)const 11 { 12 if(y == b.y) 13 return x<b.x; 14 return y<b.y; 15 } 16 }f[100005],g[100008];double xx,yy; 17 double dis(ff a,ff b) 18 { 19 return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)); 20 } 21 double cross(ff a,ff b,ff c) 22 { 23 return (b.x - a.x)*(c.y-a.y)-(c.x - a.x)*(b.y - a.y); 24 }//求叉积 25 bool cmp(ff a,ff b) 26 { 27 double m = cross(f[0],a,b); 28 if(m>0) 29 return 1; 30 if(m==0&&dis(f[0],a)-dis(f[0],b)<=0) 31 return 1; 32 return 0; 33 } 34 int main() 35 { 36 int n; 37 scanf("%d",&n); 38 for ( int i = 0;i<n;++i) scanf("%lf%lf",&f[i].x,&f[i].y); 39 sort(f,f+n);//找到最左最下的点 40 if(n == 1) 41 printf("%.2f\n",0.00); 42 else if(n == 2) 43 printf("%.2f\n",dis(f[0],f[1])); 44 else 45 { 46 g[0] = f[0];//凸包数组放入起点 47 xx = g[0].x; 48 yy = g[0].y; 49 sort(f+1,f+n,cmp);//极角排序 50 g[1] = f[1];//第二个点也在凸包内 51 int cnt = 1;//指向凸包数组的尾结点 52 for(int i = 2;i<n;++i) 53 { 54 while(i>=1&&cross(g[cnt-1],g[cnt],f[i])<0) 55 cnt--;//如果新加进来的节点相对最后两点连线偏右,减去最后一个点,循环判断 56 g[++cnt] = f[i];//加入新节点 57 } 58 double s = 0; 59 60 for (int i = 1;i<=cnt;++i) 61 s+=dis(g[i-1],g[i]); 62 s+=dis(g[cnt],f[0]);//计算每条边的长度 63 printf("%.2lf",s); 64 } 65 return 0; 66 }
题目:https://www.luogu.com.cn/problem/P2742
借鉴:https://www.cnblogs.com/aiguona/p/7232243.html