1. 程式人生 > 實用技巧 >【題解】「UVA11626」Convex Hull

【題解】「UVA11626」Convex Hull

凸包模板題。

之前寫過拿 Graham 演算法求凸包的,為了不重複/多學點知識,那這次拿 Andrew 演算法求凸包吧qaq

*此文章所有圖片均為作者手畫。


Andrew 演算法

假設我們有這些點:

首先把所有點以橫座標為第一關鍵字,縱座標為第二關鍵字排序。

相對於 Graham 演算法來說,Andrew 演算法排序更簡單,按 \(x, y\) 座標排序,時間複雜度也更低(一般的座標系中排序方法)。

首先將 \(p_1\) 入棧。

然後也將 \(p_2\) 入棧,\(p_2\) 可能在,也可能不在,等著之後判斷。

隨後,發現 \(p_3\) 偏右,所以我們將 \(p_2\) 出棧。

發現 \(p_4\)

依然偏右,\(p_3\) 出棧,\(p_4\) 入棧。

\(p_5\) 向右,\(p_4\) 出棧,\(p_5\) 入棧。

\(p_6\) 向左,入棧。

\(p_7\) 向右,\(p_6\) 出棧,\(p_7\) 入棧。

\(p_8\) 向右,\(p_7\) 出棧,繼續檢查發現相對於 \(p_5\) \(p_8\) 仍然向右,\(p_5\) 出棧,\(p_8\) 入棧。

此時,我們發現,凸包明明還空一半就到頭了???

然而這是意料之中,我們這種演算法必然會只算出一半的凸包。

所以我們需要再從排序末尾的點(也就是 \(p_8\))出發,按照一模一樣的方式再算一遍就行了。

當然如果我們走過的點就不許要再走了(除了 \(p_1\)

\(p_8\)\(p_7\),向左,\(p_7\) 入棧。

\(p_6\) 向右,\(p_7\) 出棧,\(p_6\) 入棧。

\(p_5\) 向左,入棧。

\(p_4\) 向左,入棧。

\(p_3\) 向右,\(p_4\) 出棧,對於 \(p_5\) \(p_3\) 依然向右,\(p_5\) 出棧,\(p_3\) 入棧。

\(p_2\) 向右,\(p_3\) 出棧,\(p_2\) 入棧。

最後將 \(p_2\)\(p_1\) 連起來。

至此,我們的 Andrew 演算法就完成了!

掃描的時間複雜度:\(O(n)\)(已過濾常數)

排序時間複雜度:\(O(n \log n)\)

總時間複雜度:\(O(n \log n)\)


Code

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<string>
#define line cout << endl
using namespace std;
const int NR = 1e5 + 5;
const double eps = 1e-7;
int n;
struct point {
    double x, y;
    point () {}
    point (double a, double b) : x (a), y (b) {}
    bool operator < (const point &b) const {
        if (x < b.x) return 1;
        if (x > b.x) return 0;
        return y < b.y;
    }
    point operator - (const point &b) {
        return point (x - b.x, y - b.y);
    }
};
point p[NR], sp[NR];
int cmp (double x) {
    if (fabs (x) < eps) return 0;
    return x > 0 ? 1 : -1;
}
double dis (point a, point b) {
    return sqrt ((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}
double cp (point a, point b) {
    return a.x * b.y - a.y * b.x;
}
int Andrew () {
    sort (p + 1, p + 1 + n);
    int len = 0;
    for (int i = 1; i <= n; i++) {
        while (len > 1 && cmp (cp (sp[len] - sp[len - 1], p[i] - sp[len - 1])) < 0) 
            len--;
        sp[++len] = p[i];
    }
    int k = len;
    for (int i = n - 1; i >= 1; i--) {
        while (len > k && cmp (cp (sp[len] - sp[len - 1], p[i] - sp[len - 1])) < 0)
            len--;
        sp[++len] = p[i];
    }
    return len;
}
int main () {
    int t;
    cin >> t;
    while (t--) {
        cin >> n;
        char c;
        for (int i = 1; i <= n; i++)
            cin >> p[i].x >> p[i].y >> c;
        int t = Andrew();
        cout << t - 1 << endl;
        for (int i = 1; i < t; i++) 
            printf ("%.0lf %.0lf\n", sp[i].x, sp[i].y);
    }
    return 0;
}

謝謝qaq