UVa 596 The Incredible Hull

题目描述

题目要求计算多个简单多边形的点集的凸包,并输出凸包的顶点。凸包顶点按逆时针顺序输出,起始点为 xxx 坐标最大的点(若相同取 yyy 最小的)。输出时保留所有共线的凸包顶点(即不删除共线点)。

输入格式

输入包含多行。每行以 SPEND 开头。S 开始一个新集合,后跟集合标识符。P 定义一个多边形,后跟顶点数及坐标。END 为最后一行。

输出格式

对于每个集合,输出一行标识符 convex hull:,然后一行输出凸包顶点坐标,格式如 (x,y),顶点之间用空格分隔。

样例

输入

S Sample 1
P 5 8 0 8 8 0 8 5 6 2 3
P 3 6 13 2 18 2 13
P 4 15 6 15 14 10 14 10 6
S Sample 2
P 8 1 2 -3 5 1 8 -3 12 -7 8 -3 5 -7 2 -3 -2
S Sample 3
P 4 150 100 150 150 100 150 100 100
P 4 180 130 180 180 130 180 130 130
S Sample 4
P 4 20 5 10 10 0 5 10 0
P 4 20 20 10 25 0 20 10 15
P 4 20 35 10 40 0 35 10 30
END

输出

Sample 1 convex hull:
 (15,6) (15,14) (2,18) (0,8) (2,3) (8,0)
Sample 2 convex hull:
 (1,2) (1,8) (-3,12) (-7,8) (-7,2) (-3,-2)
Sample 3 convex hull:
 (180,130) (180,180) (130,180) (100,150) (100,100) (150,100)
Sample 4 convex hull:
 (20,5) (20,20) (20,35) (10,40) (0,35) (0,20) (0,5) (10,0)

题目分析

本题的核心是计算点集的凸包,并保留共线点。可以使用 Graham\texttt{Graham}Graham 扫描法或 Jarvis\texttt{Jarvis}Jarvis 步进法。

算法

  • 收集所有多边形的顶点,去重。
  • 使用 Jarvis\texttt{Jarvis}Jarvis 步进法(礼品包装法)求凸包。在共线时,选择距离当前点最近的点,以保留所有共线顶点。
  • 找到 xxx 最大(若相同则 yyy 最小)的点作为起始点,然后按逆时针顺序输出。

复杂度分析

顶点数 ≤20×20=400\le 20 \times 20 = 40020×20=400Jarvis\texttt{Jarvis}Jarvis O(nh)O(nh)O(nh) 可接受。

代码实现

// The Incredible Hull
// UVa ID: 596
// Verdict: Accepted
// Submission Date: 2016-08-13
// UVa Run Time: 0.000s
//
// 版权所有(C)2016,邱秋。metaphysis # yeah dot net

#include <bits/stdc++.h>
using namespace std;

const int MAX_VERTICES = 500;

struct point {
	int x, y;
	bool operator<(const point& another) const {
	    if (x != another.x) return x < another.x;
	    else return y < another.y;
	}
	bool operator==(const point& another) const {
	    return x == another.x && y == another.y;
	}
};

struct polygon {
	int vertexNumber;
	point vertex[MAX_VERTICES];
};

point vertex[MAX_VERTICES];
int vertexPerObject, vertexOfTotal = 0;

// 叉积,判断点a,b,c组成的两条线段的转折方向。当叉积大于0,则形成一个右拐,否则共线(cp = 0)或左拐(cp < 0)
int cp(point a, point b, point c) {
	return (b.x - a.x) * (c.y - a.y) - (c.x - a.x) * (b.y - a.y);
}

// 从点a向点b望去,点c位于线段ab的右侧,返回true
bool cw(point a, point b, point c) {
	return cp(a, b, c) < 0;
}

// 从点a向点b望去,点c位于线段ab的左侧时,返回true
bool ccw(point a, point b, point c) {
	return cp(a, b, c) > 0;
}

// 当三点共线时,返回true
bool collinear(point a, point b, point c) {
	return abs(cp(a, b, c)) == 0;
}

int distanceOfPoint(point a, point b) {
    return pow(a.x - b.x, 2) + pow(a.y - b.y, 2);
}

// Jarvis步进法求凸包
void jarvisConvexHull() {
    polygon pg;
    pg.vertexNumber = 0;

	// 去掉重合点
	// 得到位置处于最左的点,当有共线情况存在时,取y坐标最小的,该顶点定为凸包上的顶点
	sort(vertex, vertex + vertexOfTotal);
	vertexOfTotal = unique(vertex, vertex + vertexOfTotal) - vertex;

    int current = 0, visited[MAX_VERTICES];
    memset(visited, 0, sizeof(visited));

    do
    {
        // 寻找凸包的下一个候选顶点
        // 测试点current,next,i是否构成一个右转,或者共线
        // 当构成右转时,说明点i比next相对于current有更小的极角,应该将当前的待选凸包点更新为点i
        // 当共线时,因为题意要求输出共线的凸包顶点,故选择距离当前凸包点current更近的点
        int next = 0;
        for (int i = 1; i < vertexOfTotal; i++) {
            if (!visited[i] &&
                (cw(vertex[current], vertex[next], vertex[i]) ||
                distanceOfPoint(vertex[current], vertex[next]) == 0 ||
                (collinear(vertex[current], vertex[next], vertex[i]) &&
                (distanceOfPoint(vertex[current], vertex[i]) <
                distanceOfPoint(vertex[current], vertex[next])))))
                next = i;
        }
        // 将点next加入凸包
        pg.vertex[pg.vertexNumber++] = vertex[next];
        visited[next] = 1;
        current = next;
    } while (current != 0);
    int selected = 0;
    for (int i = 0; i < pg.vertexNumber; i++)
        if (pg.vertex[i].x > pg.vertex[selected].x ||
            (pg.vertex[i].x == pg.vertex[selected].x && pg.vertex[i].y < pg.vertex[selected].y))
                selected = i;
    for (int i = selected; i < pg.vertexNumber + selected; i++)
        cout << " (" << pg.vertex[i % pg.vertexNumber].x << ',' << pg.vertex[i % pg.vertexNumber].y << ')';
    cout << '\n';
}

int main() {
    cin.tie(0); cout.tie(0); ios::sync_with_stdio(false);
    char start;
    while (cin >> start, start != 'E') {
        string description;
        getline(cin, description);
        if (description.front() == ' ') description.erase(description.begin());
        cout << description << " convex hull:\n";
        vertexOfTotal = 0;
        while (cin >> start, start == 'P') {
            cin >> vertexPerObject;
            for (int i = 0; i < vertexPerObject; i++) {
                cin >> vertex[vertexOfTotal].x >> vertex[vertexOfTotal].y;
                vertexOfTotal++;
            }
        }
        jarvisConvexHull();
        cin.putback(start);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值