AcWing算法进阶课 计算几何 凸包

 

凸包

什么是凸包

假设平面上现在有若干个点,现在我们过某些点做一个多边形,是这个多边形能够将所有点都包含起来【在多边形边上也算】。当这个多边形是一个凸多边形的时候,我们称它问凸包。

可以想象为给这些点外面箍上一个橡皮筋。

解决凸包问题的算法

暴力

时间复杂度为O(n^3)

思路:先确定两个点,然后枚举剩下的其他所有点,如果其他所有点都在这条直线的同一侧,则这两个点是凸包上的点,否则就不是。

解决:

1.枚举点对(将所有的点两两配对)组成的直线,对于每条直线,检查是否剩下的(n-2)个点是否在直线的同侧。

假设枚举的点对是a和b,而新的点是c,那么我们只需要判断他们的叉积是否同时为正或者同时为负就OK了。

 

Andrew算法

时间复杂度O(nlogn)【时间主要是在对坐标的快排上】

Andrew算法:Andrew算法是对Graham扫描法的改进。-

原理:

利用夹角,让整个图形保持左转。先将最左边的前两个点加入栈中,每次加入新点时判断是否左拐(叉积大于0),如果是就将新点直接加入;如果不是,就弹出栈顶,直到左拐,将新点加入栈中。【注意,栈中要保证至少有一个元素,也就是top>=2的时候才可以弹出栈顶】第一遍找的都是y小的点,也就是下凸包,上面没有,然后我们反着再来一遍。

流程:

1.将所有点进行快排,以x为第一关键字,y为第二关键字升序排序

2.先从左至右维护凸包的下半部分,然后再从右至左谓语上半部分。

3.将第一个点放入栈中,【这个点一定时凸包的最左边的点了,是不会清理掉的】,然后在将第二个点放入栈中。当栈中元素大于等于2的时候,就要判断栈顶元素是否还要保留。

如果新点在栈顶元素和次栈顶元素所组成的直线的右侧,那么,直接将新点加入栈中。

如果新点在栈顶元素和次栈顶元素所组成的直线的左侧,那么,将栈顶元素不断弹出,直到新点的位置出现在栈顶元素与次栈顶元素所在直线的右侧结束。

那么,我们这个过程,是从左往右走的,而且每次找的点都是在当前直线的右侧,也就是直线的下方向,那么我们得到的凸包就是我们的下半部分。

求上半部分的时候,从右往左排就自然而然是对的了。

 

代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 10005;
struct Convex_hell{
 struct Point{
   double x, y;
   //重载小于号,用于sort排序
   bool operator< (const Point &W) const{
     if (x == W.x) return y < W.y;
     return x < W.x;
  }
   //重载减号,用于计算向量
   Point operator- (const Point &W) const{
     return {x - W.x, y - W.y};
  }
};
 int n;
 Point q[N];
 int stk[N], top = 0;
 bool used[N];
 //获得两点之间的距离
 double get_dist(Point a, Point b)
{
   double dx = a.x - b.x;
   double dy = a.y - b.y;
   return sqrt(dx * dx + dy * dy);
}
 //获得向量叉积
 double cross(Point a, Point b)
{
   return a.x * b.y - a.y * b.x;
}
 //将三个点转为向量ab,和ac,计算向量叉积,如果是正说明点c在向量的左侧,是求凸包的方向。
 double area(Point a, Point b, Point c)
{
   return cross(b - a, c - a);
}
 //andrew算法,求凸包
 double andrew()
{
   sort(q, q + n);
   for (int i = 0; i < n; i ++ )
  {
     while (top >= 2 && area(q[stk[top - 1]], q[stk[top]], q[i]) <= 0)
    {
       if (area(q[stk[top - 1]], q[stk[top]], q[i]) < 0)
         used[stk[top -- ]] = false;
       else top --;
    }
     stk[++ top] = i;
     // for (int i = 1; i <= top; i ++ ) cout << q[stk[i]].x << " " << q[stk[i]].y << endl;
     // printf("---------------------\n");
     used[i] = true;
  }
   // printf("<---------I am the happiness divider--------->\n");
   used[0] = false;
   for (int i = n - 1; i >= 0; i -- )
  {
     if (used[i]) continue;
     while (top >= 2 && area(q[stk[top - 1]], q[stk[top]], q[i]) <= 0)
       top --;//因为不需要used标记了,所以才显得这么短
     stk[++ top] = i;
     // for (int i = 1; i <= top; i ++ ) cout << q[stk[i]].x << " " << q[stk[i]].y << endl;
     // printf("---------------------\n");
  }
   double res = 0;
   for (int i = 2; i <= top; i ++ )
     res += get_dist(q[stk[i - 1]], q[stk[i]]);
   return res;
}
}O;
int main()
{
 cin >> O.n;
 for (int i = 0; i < O.n; i ++ ) scanf("%lf%lf", &O.q[i].x, &O.q[i].y);
 printf("%.2lf\n", O.andrew());
 return 0;
}

 

 

posted @ 2021-07-14 19:39  rookie161  阅读(152)  评论(0编辑  收藏  举报