📜  门|门 IT 2008 |第 81 题(1)

📅  最后修改于: 2023-12-03 15:12:46.931000             🧑  作者: Mango

门|门 IT 2008 |第 81 题

这是一道来自门|门 IT 2008年的编程题,旨在考察程序员对算法的掌握和实现能力。

题目描述

有一张无边无界的白纸,上面有 $n$ 个点。现按极角排序将这些点标号为 $1,2,\ldots, n$。现从第 $1$ 点开始逆时针依次连接所有的点,形成一个凸包。求凸包周长的整数部分。

算法思路

凸包算法的经典思路是 Graham 扫描法。它的基本步骤如下:

  1. 用极角排序将所有点按逆时针顺序排序。
  2. 将前两个点加入堆栈,从第 $3$ 个点开始,对每个点执行以下操作:
    1. 如果该点在当前凸包外,将其加入堆栈。
    2. 如果该点在当前凸包内,在堆栈中不断弹出点,直到该点成为栈顶的两个点之间的凸包边的右侧点,随即将该点加入堆栈。
    3. 如果该点在当前凸包边界上,放弃该点。
  3. 所有点处理完后,堆栈中剩下的点就是凸包上的所有点。计算凸包周长,输出整数部分。
代码实现

以下是一个使用 Python 实现 Graham 扫描法求凸包周长的示例程序:

def cross(x1, y1, x2, y2, x3, y3):
    return (x2 - x1) * (y3 - y2) - (x3 - x2) * (y2 - y1)

def graham_scan(n, x, y):
    p = [(x[i], y[i]) for i in range(n)]
    p.sort()
    stk = []
    for i in range(n):
        while len(stk) >= 2 and cross(stk[-2][0], stk[-2][1], stk[-1][0], stk[-1][1], p[i][0], p[i][1]) < 0:
            stk.pop()
        stk.append((p[i][0], p[i][1]))
    for i in range(n - 1, -1, -1):
        while len(stk) >= 2 and cross(stk[-2][0], stk[-2][1], stk[-1][0], stk[-1][1], p[i][0], p[i][1]) < 0:
            stk.pop()
        stk.append((p[i][0], p[i][1]))
    ans = 0
    for i in range(len(stk) - 1):
        ans += (stk[i + 1][0] - stk[i][0]) ** 2 + (stk[i + 1][1] - stk[i][1]) ** 2
    return int(ans ** 0.5 + 0.5)

n = int(input())
x = []
y = []
for i in range(n):
    xi, yi = map(int, input().split())
    x.append(xi)
    y.append(yi)
print(graham_scan(n, x, y))

代码中 cross() 函数计算 $3$ 个点构成的向量叉积,graham_scan() 函数实现了 Graham 扫描法的流程,最后将答案四舍五入输出。注意,这里的求凸包周长用到了勾股定理,但由于 Python 中整数空间大小无限,所以没有必要使用 sqrt() 函数求根,直接将答案取平方再四舍五入即可。