[学习笔记]省选算法·计算几何·凸包

一、开头

(湖南省神犇协会)
神犇1号:全体神犇,一起去虐HNOI,顺便D一下xyz32768!
(X省Y市)
xyz32768:你们虐完HNOI来干什么啊?不会又要来D人了吧!
神犇256号:哈哈,我们这里有 217 2 17 个柱子,我们想选一些柱子,用这些柱子围成一个凸多边形,并且这个凸多边形必须包含所有的柱子。我会,但我就是不告诉你,就看看你会不会了。
xyz32768:让我想一想………………………………
神犇65536号:如果想不出来的话,我们会随便找一个柱子把你阿掉!
xyz32768:什么?我想不出来了!你们怎么虐完了题还要阿人啊!
神犇1号&神犇256号&神犇65536号&神犇16777216号:我们四个人一起上,把xyz32768阿掉,快追上去!
神犇20号:xyz32768你怎么这么菜啊,凸包多水啊!

二、定义

一个 n n 个点的点集,选出一个子集,这个子集的点能构成一个凸多边形,并且这个凸多边形能覆盖所有的n个点,那么这个凸多边形就是这 n n 个点的点集的凸包。开头中神犇256号提出的问题就是给定一个217个点的点集,求这个点集的凸包。给出一个例子:
这里写图片描述

三、求解

求解凸包的常用算法是Graham扫描法。
先取一个 x x 坐标最小(相同情况下y坐标最小)的点作为极点,这个点一定在凸包上。
然后把其余的点按照逆时针极角排序(相同情况下按照到极点的距离排序),这一过程可以用叉积实现,不用真正求出极角。即( O O 为极点,θA表示点 A A 的极角):

θA<θBOA×OB>0

例( 1 1 号点为极点,其余的点的编号按照极角大小递增):
这里写图片描述
算法实现:用一个栈记录凸包中的点,首先将极点入栈,然后按照逆时针极角大小顺序扫描每个点,扫描到每个点时,
(1)如果栈内的点不足2个,则把这个点加入栈,并结束这一步。
(2)否则设栈顶的点为 Y Y ,栈顶的下一个点为X,待加入的点为 Z Z ,检查线段XY与线段 YZ Y Z 构成的折线段是否拐向右(顺时针方向)(可以用叉积判断,也就是 XY×XZ<0 X Y → × X Z → < 0 ),如果满足则退栈,继续检查栈顶的 2 2 个点,直到不满足或者栈中不足2个点为止。
扫描完所有的点之后,栈中剩余的点就是凸包。
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
由于一个点最多只入栈和出栈一次,因此扫描的复杂度为 O(n) O ( n ) ,但由于极角排序的存在,总复杂度为 O(nlogn) O ( n log ⁡ n )

四、代码

算法的主体部分非常短。

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
const int N = 1e4 + 5;
struct cyx {
    int x, y; cyx() {} cyx(int _x, int _y) : x(_x), y(_y) {}
    friend inline cyx operator - (cyx a, cyx b) {
        return cyx(b.x - a.x, b.y - a.y);
    }
    friend inline int operator * (cyx a, cyx b) {
        return a.x * b.y - a.y * b.x;
    }
} a[N]; int n, top, stk[N];
bool comp(cyx x, cyx y) {
    int orz = (a[1] - x) * (a[1] - y);
    if (orz != 0) return orz > 0; if (x.y != y.y) return x.y < y.y;
    return x.x < y.x;
}
void solve() {
    int i; stk[top = 1] = 1; for (i = 2; i <= n; i++) {
        while (top > 1 && (a[stk[top - 1]] - a[stk[top]])
        * (a[stk[top - 1]] - a[i]) < 0) top--; stk[++top] = i;
    }
}
int main() {
    int i, tmp = 1; n = read(); for (i = 1; i <= n; i++)
        a[i].x = read(), a[i].y = read();
    for (i = 2; i <= n; i++) if (a[i].x < a[tmp].x ||
        (a[i].x == a[tmp].x && a[i].y < a[tmp].y)) tmp = i;
    if (tmp != 1) swap(a[1], a[tmp]); sort(a + 2, a + n + 1, comp);
    solve(); return 0;
}

五、题目

(待补充)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值