凸包【模板】

hicc0305

2018-04-23 10:54:22

Personal

一看到凸包我是懵逼的,心想这是什么难度,我这种蒟蒻能学会么。 一看发现还是蛮简单的哈 我这里介绍常用的Graham扫描法,时间复杂度是nlogn 要学这种算法,先要知道一种叫叉积的东西 对于点A(x1,y1),B(x2,y2),C(x3,y3),向量AB与向量AC的叉积为(x2-x1)(y3-y1)-(x3-x1)(y2-y1),若叉积大于零,逆时针上两条边的夹角小于180°,反之则大于180°,等于零则共线。C在直线AB的逆时针方向,还是顺时针方向,简单来说,也就是在上面还是在下面。 根据常识,我们可以知道最左下角的点一定是凸包上的(你喜欢最左上,最右上。。也一样)。然后我们可以通过这个点去扩展出凸包,like this: 先加入点A、C ![](https://cdn.luogu.com.cn/upload/pic/17938.png) 插入第三个点G ![](https://cdn.luogu.com.cn/upload/pic/17940.png) 计算AC×CG的叉积,却发现叉积小于0,也就是说逆时针方向上∠ACG大于180度,于是删去C点,加入G点 ![](https://cdn.luogu.com.cn/upload/pic/17941.png) 同理,一步一步的做 ![](https://cdn.luogu.com.cn/upload/pic/17939.png) 那怎么找到点C呢?以A为原点,对其他点进行极角排序,也就是把A和其他点的连线按辐角排序。辐角是什么呢 ![](https://cdn.luogu.com.cn/upload/pic/17942.png) 就是这样,于是辐角最小的就是C点啦,用脚想想都知道C必定得选的,当然辐角最大的也必定得选。你可以从辐角最小的点逆时针扩展,当然也可以从辐角最大的点顺时针扩展。 程序详细步骤如下: 1.把所有点放在二维坐标系中,则纵坐标最小的点一定是凸包上的点 2.把所有点的坐标平移一下,使 A 作为原点 3.计算各个点相对于 A 的幅角 α ,按从小到大的顺序对各个点排序。当 α 相同时,距离 A 比较近的排在前面。我们由几何知识可以知道,结果中第一个点 B 和最后一个点一定是凸包上的点。 (以上是准备步骤,以下开始求凸包) 以上,我们已经知道了凸包上的第一个点 A 和第二个点 B,我们把它们放在栈里面。s[1]=A,s[2]=B.现在从步骤3求得的那个结果里,把 B 后面的那个点拿出来做当前点。此时栈顶指针top=2 4.连接s[top-1]和s[top]这两个点,得到直线 L 。看当前点是在直线 L 的右边还是左边。如果在直线的右边就执行步骤5;如果在直线上,或者在直线的左边就执行步骤6。 5.如果在右边,则s[top]不是凸包上的点,把栈顶元素出栈。执行步骤4。 6.当前点是凸包上的点,把它压入栈,执行步骤7。 7.检查当前点是不是步骤3那个结果的最后一个元素。是最后一个元素的话就结束。如果不是的话就把 P2 后面那个点做当前点,返回步骤4。 最后,栈中的元素就是凸包上的点了。 ------------ 程序如下 ```cpp #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; int n; struct node { int x,y; }p[50100],s[100100]; int cross(node a,node b,node c)//计算叉积 { return (b.x-a.x)*(c.y-a.y)-(c.x-a.x)*(b.y-a.y); } int dis(node a,node b) { return (a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y); } bool cmp1(node a,node b) { if(a.y==b.y) return a.x<b.x; return a.y<b.y; } bool cmp2(node a,node b)//极角排序 { int k=cross(s[1],a,b); if(k>0) return 1; else if(k==0 && dis(s[1],a)<=dis(s[1],b)) return 1;//共线的话就把距离近的排在前面 else return 0; } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d%d",&p[i].x,&p[i].y); sort(p+1,p+n+1,cmp1);//按横纵坐标排序,得出最左下角的点 s[1]=p[1]; sort(p+2,p+n+1,cmp2);//极角排序 s[2]=p[2];//得到辐角最小的点,入栈 int top=2; for(int i=3;i<=n;i++) { while(cross(s[top-1],s[top],p[i])<0) top--;//比较当前点在直线的右边还是左边。在右边就一直弹栈顶 s[++top]=p[i];//进栈 } for(int i=1;i<=top;i++) cout<<s[i].x<<" "<<s[i].y<<endl; return 0; } ```