📜  具有整数相交点的线对数(1)

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

具有整数相交点的线对数

对于给定的平面上的$n$条线段,求其中有多少对线段相交,且交点的坐标均为整数。

解法
思路

首先注意到交点坐标为整数这个条件,在$x$轴和$y$轴上的投影非常重要。

对于每一条线段,我们将其沿其在$x$轴上的投影扫描进入一个事件队列,每当我们遇到一个新线段的左端点或右端点时,我们就更新一下正在处理的线段集合,计算其中互相相交的线段对数。

具体地,我们维护一个平衡树,用来存储当前正在处理的所有线段,以及它们的交点。

每当遇到一个新的左端点时,我们将它插入平衡树中,然后查找它前驱和后继,看看它是否和它的前驱(如果有的话)、后继(如果有的话)发生了相交。如果发生了相交,则将其加入答案。

每当遇到一个右端点时,我们将它从平衡树中删除,然后对于它的前驱和后继,检查是否原来它们相交,但是现在没有相交了。如果发生了这种情况,则将其从答案中删除。

代码
from typing import List, Tuple
from bisect import bisect_left, bisect_right

class BalancedSearchTree:
    def __init__(self):
        self.lines = []
        self.points = set()
        self.events = []

    def add_line(self, line: Tuple[int, int, int, int]) -> None:
        """
        Add a line to the BST.
        """
        x1, y1, x2, y2 = line
        if y1 == y2:
            if x1 > x2:
                x1, x2 = x2, x1
            self.points.add((x1, y1))
            self.points.add((x2, y2))
            self.events.append((y1, ("left", (x1, y1), line)))
            self.events.append((y2, ("right", (x2, y2), line)))
        else:
            if y1 > y2:
                x1, x2, y1, y2 = x2, x1, y2, y1
            self.lines.append((y1, ("left", (x1, y1), line)))
            self.lines.append((y2, ("right", (x2, y2), line)))

    def remove_line(self, line: Tuple[int, int, int, int]) -> None:
        """
        Remove a line from the BST.
        """
        x1, y1, x2, y2 = line
        if y1 == y2:
            if x1 > x2:
                x1, x2 = x2, x1
            self.points.remove((x1, y1))
            self.points.remove((x2, y2))
            self.events.remove((y1, ("left", (x1, y1), line)))
            self.events.remove((y2, ("right", (x2, y2), line)))
        else:
            if y1 > y2:
                x1, x2, y1, y2 = x2, x1, y2, y1
            self.lines.remove((y1, ("left", (x1, y1), line)))
            self.lines.remove((y2, ("right", (x2, y2), line)))

    def count_intersections(self) -> int:
        """
        Count the number of intersections of the lines in the BST.
        """
        self.events.sort()
        self.lines.sort()
        active = []
        ans = 0
        for y, (kind, point, line) in self.events:
            if kind == "left":
                x, y = point
                active.append(line)
                i = bisect_left(self.lines, (y,)) - 1
                while i >= 0 and self.lines[i][1][0] == "left":
                    l = self.lines[i][1][1]
                    if x <= l[0]:
                        break
                    if is_interior_point((x, y), l):
                        ans += 1
                    i -= 1
                i = bisect_right(self.lines, (y,)) - 1
                while i < len(self.lines) and self.lines[i][1][0] == "left":
                    l = self.lines[i][1][1]
                    if x >= l[2]:
                        break
                    if is_interior_point((x, y), l):
                        ans += 1
                    i += 1
            else:
                x, y = point
                i = bisect_left(self.lines, (y,)) - 1
                while i >= 0 and self.lines[i][1][0] == "left":
                    l = self.lines[i][1][1]
                    if x < l[0]:
                        break
                    if is_interior_point((x, y), l):
                        ans -= 1
                    i -= 1
                i = bisect_right(self.lines, (y,)) - 1
                while i < len(self.lines) and self.lines[i][1][0] == "left":
                    l = self.lines[i][1][1]
                    if x > l[2]:
                        break
                    if is_interior_point((x, y), l):
                        ans -= 1
                    i += 1
                active.remove(line)
        return ans

def is_interior_point(point: Tuple[int, int], line: Tuple[int, int, int, int]) -> bool:
    """
    Check if a given point is an interior point of a line segment.
    """
    x, y = point
    x1, y1, x2, y2 = line
    if x1 > x2 or (x1 == x2 and y1 > y2):
        x1, x2 = x2, x1
        y1, y2 = y2, y1
    if x < x1 or x > x2:
        return False
    # Check that (y-y1)/(x-x1) = (y2-y1)/(x2-x1) has integer solutions.
    return (y-y1)*(x2-x1) == (x-x1)*(y2-y1) and (y-y1) % (y2-y1) == 0

def count_integer_intersection_points(lines: List[Tuple[int, int, int, int]]) -> int:
    """
    Given a list of line segments, count the number of pairs that intersect at an integer point.
    """
    bst = BalancedSearchTree()
    for line in lines:
        bst.add_line(line)
    ans = bst.count_intersections()
    for line in lines:
        bst.remove_line(line)
    return ans

其中,add_line函数将一条线段加入平衡树中,remove_line函数移除一条线段,count_intersections函数计算线段的交点个数,is_interior_point函数检查一个点是否是一个线段的内部点,最后count_integer_intersection_points函数调用上述函数计算答案。