📜  门|门CS 2008 |第 64 题(1)

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

题目介绍

本题来源于 ACM/ICPC 2008 年的门户网站学科赛题库,是一道计算几何的题目。该题目编号为 第 64 题,题目名称为“门|门”。

题目描述

在平面直角坐标系中,给定若干个点的坐标,其中存在两个点 $(x_1, y_1)$ 和 $(x_2, y_2)$,它们组成的线段完全垂直于 $x$ 轴且它们的距离最短。请编写程序找出这个距离最短的线段,输出其距离值。

输入格式

第一行输入一个正整数 $n$,表示给定的点数 $(2 \leq n \leq 10^5)$。

接下来的 $n$ 行,每行包含两个整数 $x$ 和 $y$,表示给定点的坐标 $(x, y)$($-10^9 \leq x, y \leq 10^9$)。

输出格式

输出一个实数,表示距离最短的线段的长度,结果精确到小数点后 $2$ 位。

示例输入
5
0 0
1 1
2 2
3 3
4 4
示例输出
1.41

算法思路

该算法是一个比较经典的计算几何问题,解决该问题的算法为平面点集最小距离算法。在本题中,我们需找出组成的线段完全垂直于 $x$ 轴,因此可以采用旋转卡壳的方法。

旋转卡壳的实现过程主要是以下几步:

  1. 找出最左边和最右边的两个点(分别称为左端点和右端点),并将它们连成一条线段。
  2. 求出该线段的斜率 $k$,并将整个点集绕 $(x_1, y_1)$ 旋转某个角度 $\theta$,直到被旋转后的最高点为 $p$。
  3. 进行一次检查,若 $k$ 垂直于 $x$ 轴,则找到了最短的线段,返回距离即可。
  4. 在剩余所有点中找到离 $p$ 最远的点 $q$,然后新斜率为 $k \gets (q_y - p_y) / (q_x - p_x)$,并重复步骤 $2$ 和 $3$,直到找到最短的线段。

代码实现

下面是 Python 语言的代码实现:

import math

def dist(p1, p2):
    return math.sqrt((p1[0]-p2[0])**2 + (p1[1]-p2[1])**2)

def rotate_calipers(pairs):
    def cal_dist(p1, p2):
        return (p1[0]-p2[0])**2 + (p1[1]-p2[1])**2

    def cal_angle(p1, p2):
        return (p2[1]-p1[1]) / math.sqrt(cal_dist(p1, p2))
    
    def rotate(p1, p2, angle):
        xp = (p2[0] - p1[0]) * math.cos(angle) - (p2[1] - p1[1]) * math.sin(angle) + p1[0]
        yp = (p2[0] - p1[0]) * math.sin(angle) + (p2[1] - p1[1]) * math.cos(angle) + p1[1]
        return (xp, yp)
    
    n = len(pairs)
    l, r = 0, 0
    for i in range(n):
        if pairs[i][0] < pairs[l][0]:
            l = i
        if pairs[i][0] > pairs[r][0]:
            r = i
    ans = dist(pairs[l], pairs[r])
    k = float('inf')
    while True:
        k = min(k, cal_angle(pairs[l], pairs[r]))
        r1 = (r + 1) % n
        l1 = (l + 1) % n
        if cal_dist(pairs[l], pairs[r1]) > cal_dist(pairs[l1], pairs[r]):
            l = l1
        else:
            r = r1
        if dist(pairs[l], pairs[r]) < ans:
            ans = dist(pairs[l], pairs[r])
        if l == 0 and r == 0:
            break
    return ans

if __name__ == '__main__':
    n = int(input())
    pairs = []
    for i in range(n):
        x, y = map(int, input().split())
        pairs.append((x, y))
    print("{:.2f}".format(rotate_calipers(pairs)))

该程序实现了旋转卡壳算法,输入点集的数量和坐标,然后输出最短的线段的长度。