(模板)graham掃描法、andrew算法求凸包

凸包算法講解:Click Herehtml

題目連接:https://vjudge.net/problem/POJ-1113ios

題意:簡化下題意即求凸包的周長+2×PI×r。算法

思路:用graham求凸包,模板是kuangbin的,算法複雜度O(nlogn)。spa

AC code:.net

// Author : RioTian
// Time : 20/10/21
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
using namespace std;
const int N = 1000;
const double Pi = acos(-1.0);

struct point {
    double x, y;
    point() : x(), y() {}
    point(int x, int y) : x(x), y(y) {}
} list[N];
typedef point P;
int stack[N], top;
//計算叉積: p0p1 X p0p2
double cross(P p0, P p1, P p2) {
    return (p1.x - p0.x) * (p2.y - p0.y) - (p2.x - p0.x) * (p1.y - p0.y);
}
//計算 p1p2的 距離
double dis(P p1, P p2) {
    return sqrt((double)(p2.x - p1.x) * (p2.x - p1.x) +
                (p2.y - p1.y) * (p2.y - p1.y));
}
//利用極角排序,角度相同則距離小排前面
bool cmp(P p1, P p2) {
    auto tmp = cross(list[0], p1, p2);
    if (tmp > 0)
        return true;
    else if (tmp == 0 && dis(list[0], p1) < dis(list[0], p2))
        return true;
    else
        return false;
}
//輸入,並把  最左下方的點放在 list[0]  。而且進行極角排序
void init(int n) {
    int i, k = 0;
    cin >> list[0].x >> list[0].y;
    P p0 = list[0];  // p0 等價於 tmp 去尋找最左下方的點
    for (int i = 1; i < n; ++i) {
        cin >> list[i].x >> list[i].y;
        if (p0.y > list[i].y || (p0.y == list[i].y && p0.x > list[i].x))
            p0 = list[i], k = i;
    }
    list[k] = list[0];
    list[0] = p0;
    sort(list + 1, list + n, cmp);
}
//graham掃描法求凸包,凸包頂點存在stack棧中
//從棧底到棧頂一次是逆時針方向排列的
//若是要求凸包的一條邊有2個以上的點
//那麼要將while中的<=改爲<
//但這不能將最後一條邊上的多個點保留
//由於排序時將距離近的點排在前面
//那麼最後一條邊上的點僅有距離最遠的會被保留,其他的會被出棧
//因此最後一條邊須要特判
//若是要求逆凸包的話須要改cmp,graham中的符號便可
void Graham(int n) {
    int i;
    if (n == 1) top = 0, stack[0] = 0;
    if (n == 2) top = 1, stack[0] = 0, stack[1] = 1;
    if (n > 2) {
        for (i = 0; i <= 1; i++) stack[i] = i;
        top = 1;

        for (i = 2; i < n; i++) {
            while (top > 0 &&
                   cross(list[stack[top - 1]], list[stack[top]], list[i]) <= 0)
                top--;
            top++;
            stack[top] = i;
        }
    }
}
int main() {
    freopen("in.txt", "r", stdin);
    // ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int N, L;
    while (scanf("%d", &N) != EOF) {
        init(N);
        Graham(N);
        double res = 0;
        for (int i = 0; i < top; i++)
            res += dis(list[stack[i]], list[stack[i + 1]]);
        res += dis(list[stack[0]], list[stack[top]]);

        res += 2 * Pi;
        printf("%d\n", (int)(res + 0.5));
    }
}

題目連接:https://www.luogu.com.cn/problem/P2742code

題意:求凸包的周長。htm

思路:blog

  這裏用andrew算法來求,該算法與graham的區別是排序方法不同,這裏按x座標從左到右排序,x相同的按y座標從下到上排序。下列程序展現先求下凸包,再求上凸包。複雜度O(nlogn),但聽說比graham的複雜度小一點。排序

AC code:ci

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
using namespace std;

const int maxn = 1e5 + 5;

struct Point {
    double x, y;
    Point(double xx = 0, double yy = 0) : x(xx), y(yy) {}
};

double cross(Point p0, Point p1, Point p2) {
    return (p1.x - p0.x) * (p2.y - p0.y) - (p2.x - p0.x) * (p1.y - p0.y);
}
//排序方法不一樣
bool cmp(Point a, Point b) {
    if (a.x != b.x) return a.x < b.x;
    return a.y < b.y;  // y從小到大和從大到小都行
}

double dis(Point a, Point b) {
    return sqrt((b.x - a.x) * (b.x - a.x) + (b.y - a.y) * (b.y - a.y));
}

Point list[maxn], stk[maxn];
int n, p;
double ans;

void andrew() {
    p = 1;
    stk[0] = list[0], stk[1] = list[1];
    for (int i = 2; i < n; ++i) {  //求下凸包
        while (p > 0 && cross(stk[p - 1], stk[p], list[i]) <= 0) --p;
        stk[++p] = list[i];
    }
    stk[++p] = list[n - 2];
    for (int i = n - 3; i >= 0; --i) {  //求上凸包
        while (p > 0 && cross(stk[p - 1], stk[p], list[i]) <= 0) --p;
        stk[++p] = list[i];
    }  //要注意棧尾和棧頂都是list[0]
}

int main() {
    scanf("%d", &n);
    for (int i = 0; i < n; ++i) scanf("%lf%lf", &list[i].x, &list[i].y);
    sort(list, list + n, cmp);
    andrew();
    for (int i = 0; i < p; ++i) ans += dis(stk[i], stk[i + 1]);
    printf("%.2f\n", ans);
    return 0;
}
相關文章
相關標籤/搜索