凸包---graham scan算法 + 例题P2742

ToLeftTest

这是一个判断一个点在向量的左边还是右边的算法。

如上图,我们有三个点,假定分别有坐标。

  • \(a\) (\(xa, ya\))
  • \(b\) (\(xb, yb\))
  • \(c\) (\(xc, yc\))

则有向量

  • \(\vec A = b - a = (xb - xa, yb - ya)\)
  • \(\vec B = c - a = (xc - xa, yc - ya)\)

叉乘 \(\vec A \times B\) 也就是 \(\mid A \mid \mid B \mid sin \alpha\),我们可以得到:
如果 \(\vec A 和 \vec B\) 夹角是在 \((0, 2\pi)\) 之间的时候叉乘是正数,否者就是负数,这也就正好对应了 c 点是在 ab 向量的左侧还是右侧。

graham scan

graham scan算法的思想

  • 首先我们得找到一个基准点,一般取最下面而且最左边的,也就是 \(lowest\ than\ leftmost\)
  • 以这个点为基准点,在逆时针方向上找到一条最近的极边,通过逆时针扫描,对点进行排序,如果有两个点在同一条直线上的话,取离基准点最近的点序号更前。

如下图是一个完整的点排序过程

  • 从这里我们不难发现前两个点一定是极点,其连成的边也一定是级边。
  • 接下来的算法就是通过最后两个找到的点取找下一个极点。也就是对这三个点做ToLeftTest。如果满足下一个点在这最后找到的两个点的左边,则暂时先判定这个点是极点。重复操作。

下面是整个算法的演示过程

3显然在12的左边,建立级边。

对2,3,4分析发现,4在2,3的右侧,把已经找好的最后的一个点删去(也就是3),再通过对1, 2, 4检测,ToLeftTest成立,4符合要求。

后面的点,没有歧义了,最后就会变成这样。

完美的凸包构造出来了

回溯选点

我们可以看出这里已经回溯选点了一次,但是在下一次选点的时候还是不能满足,因此我们再不满足要求的时候需要不断的回溯选点

代码

这里选了一道板子题
P2742 [USACO5.1]圈奶牛Fencing the Cows /【模板】二维凸包

//Powered by CK
#include<bits/stdc++.h>
using namespace std;
const double INF = 1e100;
const double pi = acos(-1.0);
const double eps = 1e-8;
const int N = 1e5 + 10;
int n, cnt;
struct point {
    double x, y;
    point(double a = 0.0, double b = 0.0) : x(a), y(b) {}
}p[N], ans[N], fir;
int sgn(double x) {
    if(fabs(x) < eps)   return 0;
    if(x > 0)   return 1;
    return -1;
}
point operator -(point a, point b) {
    return point(a.x - b.x, a.y - b.y);
}
double cross(point a, point b) {
    return a.x * b.y - a.y * b.x;
}
double dis(point a, point b) {
    point temp = a - b;
    return sqrt(temp.x * temp.x + temp.y * temp.y);
}
bool cmp(point a, point b) {
    int flag = sgn(cross(a - fir, b - fir));
    if(flag == 1)   return true;
    if(flag == 0 && dis(fir, a) < dis(fir, b))  return true;
    return false;
}
void graham() {
    sort(p, p + n, cmp);
    ans[0] = p[0], ans[1] = p[1], cnt = 2;
    for(int i = 2; i < n; i++) {
        while(sgn(cross(ans[cnt - 1] - ans[cnt - 2], p[i] - ans[cnt - 2])) == -1)
            cnt--;
        ans[cnt++] = p[i];
    }
    ans[cnt] = ans[0];
    double target = 0;
    for(int i = 0; i < cnt; i++)
        target += dis(ans[i], ans[i + 1]);
    printf("%.2f\n", target);
}
int main() {
    // freopen("in.txt", "r", stdin);
    fir.x = fir.y = INF;
    scanf("%d", &n);
    for(int i = 0; i < n; i++) {
        scanf("%lf %lf", &p[i].x, &p[i].y);
        if(p[i].y < fir.y)  fir = p[i];
        else if(p[i].y == fir.y && p[i].x < fir.x)  fir = p[i];
    }
    graham();
    return 0;
}
posted @ 2020-04-13 19:29  lifehappy  阅读(417)  评论(0编辑  收藏  举报