結果

問題 No.3438 [Cherry 8th Tune D] 競プロは向いてない
コンテスト
ユーザー 👑 Kazun
提出日時 2024-03-03 23:40:31
言語 PyPy3
(7.3.17)
結果
AC  
実行時間 1,587 ms / 3,000 ms
コード長 8,739 bytes
記録
記録タグの例:
初AC ショートコード 純ショートコード 純主流ショートコード 最速実行時間
コンパイル時間 207 ms
コンパイル使用メモリ 82,364 KB
実行使用メモリ 125,336 KB
最終ジャッジ日時 2026-01-23 20:52:17
合計ジャッジ時間 134,995 ms
ジャッジサーバーID
(参考情報)
judge1 / judge3
このコードへのチャレンジ
(要ログイン)
ファイルパターン 結果
sample AC * 1
other AC * 33
権限があれば一括ダウンロードができます

ソースコード

diff #
raw source code

from math import sqrt,sin,cos,tan,asin,acos,atan2,pi,floor,gcd

epsilon = 1e-8
def compare(x: float, y: float, ep: float = epsilon) -> int:
    """ x,y の大小比較をする. ただし, ep の誤差は同一視する.

    Args:
        x (float):
        y (float):
        ep (float, optional): 許容誤差. Defaults to epsilon.

    Returns:
        x > y のときは 1
        x = y のときは 0
        x < y のときは -1
    """

    diff = x - y
    if diff > ep:
        return 1
    elif diff < -ep:
        return -1
    else:
        return 0

def sign(x: float, ep: float = epsilon) -> int:
    if x > ep:
        return 1
    elif x < -ep:
        return -1
    else:
        return 0

def equal(x: float, y: float, ep: float = epsilon) -> bool:
    return abs(x - y) < ep

class Point:
    __slots__ = ('x', 'y')

    def __init__(self, x: float = 0, y: float = 0):
        self.x = x
        self.y = y

    #文字列
    def __str__(self):
        return f"({self.x}, {self.y})"

    def __repr__(self):
        return f"{self.__class__.__name__}({self.x}, {self.y})"

    #Bool
    def __bool__(self):
        return (sign(self.x) != 0) or (sign(self.y) != 0)

    #等号
    def __eq__(self, other: "Point") -> bool:
        return equal(self.x, other.x) and equal(self.y, other.y)

    #不等号
    def __ne__(self, other: "Point") -> bool:
        return not (self == other)

    #比較(<)
    def __lt__(self, other: "Point") -> bool:
        if (t := compare(self.x, other.x)):
            return t < 0
        return compare(self.y, other.y) < 0

    #比較(<=)
    def __le__(self, other: "Point") -> bool:
        return self < other or self == other

    #比較(>)
    def __gt__(self, other: "Point") -> bool:
        return other < self

    #比較(>=)
    def __ge__(self, other: "Point") -> bool:
        return other <= self

    #正と負
    def __pos__(self) -> "Point":
        return self

    def __neg__(self) -> "Point":
        return Point(-self.x, -self.y)

    #加法
    def __add__(self, other: "Point") -> "Point":
        return Point(self.x + other.x, self.y + other.y)

    def __iadd__(self, other: "Point") -> "Point":
        self.x += other.x
        self.y += other.y
        return self

    #減法
    def __sub__(self, other: "Point") -> "Point":
        return Point(self.x - other.x, self.y - other.y)

    def __isub__(self, other: "Point") -> "Point":
        self.x -= other.x
        self.y -= other.y
        return self

    #乗法
    def __mul__(self, other: "Point") -> "Point":
        x, y = self.x, self.y
        u, v = other.x, other.y
        return Point(x * u- y * v, x * v + y * u)

    def __imul__(self, other: "Point") -> "Point":
        return other * self

    def __rmul__(self, other: int | float) -> "Point":
        if isinstance(other, (int, float)):
            return Point(other * self.x, other * self.y)
        raise NotImplemented

    #除法
    def __truediv__(self, other) -> "Point":
        if other == 0:
            raise ZeroDivisionError
        return Point(self.x / other, self.y / other)

    #絶対値
    def __abs__(self) -> float:
        return sqrt(self.x * self.x + self.y * self.y)

    norm = __abs__

    def norm_2(self) -> float:
        """ ノルムの 2 乗を求める

        Returns:
            float: ノルムの 2 乗
        """
        return self.x * self.x + self.y * self.y

    #回転
    def rotate(self, theta: float) -> "Point":
        """ 原点中心に theta だけ回転させた後の点を求める.

        Args:
            theta (float): 回転角

        Returns:
            Point: 回転後の点
        """
        x, y = self.x, self.y
        s, c = sin(theta), cos(theta)
        return Point(c * x - s * y , s * x + c * y)

    def __iter__(self):
        yield self.x
        yield self.y

    def __hash__(self):
        return hash((self.x,self.y))

    def latticization(self, delta: float = 1e-7):
        """ 点が格子点に十分近いとき, この点を格子点の点として修正する.

        Args:
            delta (float, optional): 判断のための閾値. Defaults to 1e-7.
        """

        if (abs(self.x - floor(self.x + 0.5)) < delta) and (abs(self.y-floor(self.y + 0.5)) < delta):
            self.x = floor(self.x+0.5)
            self.y = floor(self.y+0.5)

    def normalization(self):
        """ 向きをそのままに, 長さを 1 に変換する.
        """

        r = abs(self)
        self.x /= r
        self.y /= r

    def normal_unit_vector(self) -> "Point":
        """ 単位法線ベクトルを求める.

        Returns:
            Point: 単位法線ベクトル
        """

        assert self, ValueError

        d = self.norm()
        return Point(-self.y / d, self.x / d)

    def dot(self, other: "Point") -> float:
        """ 内積を求める

        Args:
            other (Point):

        Returns:
            Point: 内積
        """
        return self.x * other.x + self.y * other.y

    def det(self, other: "Point") -> float:
        """ 外積を求める

        Args:
            other (Point):

        Returns:
            float: 外積
        """

        return self.x * other.y - self.y * other.x

    def arg(self) -> float:
        """ 原点からみたこの点の偏角

        Returns:
            float: 偏角
        """

        return atan2(self.y,self.x)

    def copy(self):
        return Point(self.x,self.y)

def iSP(A: Point, B: Point, C: Point) -> int:
    """ A->B->C と進んだときの進行方向を見る. ※ B が中心

    Args:
        A (Point): 始点
        B (Point): 中継点
        C (Point): 終点

    Returns:
        int:
            左折 (反時計回り): +1
            右折 (時計回り): -1
            C-A-B の順に並んでいる: -2
            A-B-C の順に並んでいる: 2
            A-C-B の順に並んでいる: 0
    """

    if (p := sign((B - A).det(C - A))) != 0:
        return p

    if sign((B - A).dot(C - A)) == -1:
        return -2
    if sign((A - B).dot(C - B)) == -1:
        return 2
    return 0

class Polygon:
    __slots__ = ("vertices", )

    def __init__(self, *points: Point):
        self.vertices = list(points)

    def __str__(self):
        return f"[Polygon] {', '.join(map(str, self.vertices))}"

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}({', '.join(map(repr, self.vertices))})"

    def area(self):
        S = 0
        vertices = self.vertices
        for i in range(len(vertices) - 1):
            S += vertices[i].det(vertices[i + 1])
        S += vertices[-1].det(vertices[0])
        return abs(S) / 2

def Convex_Hull(S: list[Point], online = False) -> Polygon:
    """ S の凸包を求める

    Args:
        S (list[Point]): 点集合
        online (bool, optional): False のとき, 内角が 180 度になるのを許容しない. Defaults to False.

    Returns:
        Polygon: S の凸包
    """

    def cover(reverse_flag):
        vertices = []
        for P in (reversed(S) if reverse_flag else S):
            if not online and vertices and vertices[-1] == P:
                continue

            while len(vertices)>=2:
                m = iSP(vertices[-2], vertices[-1], P)
                if m == -1 or (not online and m == 2):
                    vertices.pop()
                else:
                    break
            vertices.append(P)
        return vertices

    S.sort()

    #上側
    upper = cover(True)

    #下側
    lower = cover(False)

    return Polygon(*(upper + lower[1:-1]))

#==================================================
def solve():
    N = int(input())

    P: list[Point] = [None] * N
    P_inv = {}
    for i in range(N):
        X, Y = map(int, input().split())
        P[i] = Point(X, Y)
        P_inv[P[i]] = i

    C = Convex_Hull(P)
    M = len(C.vertices)

    ans = ['No'] * N
    if M == 2:
        # C が直線の場合
        S, T = C.vertices
        i = P_inv[S]; j = P_inv[T]

        # (a, b) = (±1, 0), (0, ±1) を調べれば十分.
        for a, b in [(1, 0), (-1, 0), (0, 1), (0, -1)]:
            if a * S.x + b * S.y > a * T.x + b * T.y:
                ans[i] = f'{a} {b}'
                ans[j] = f'{-a} {-b}'
    else:
        # C が三角形以上の多角形のとき
        for j, S in enumerate(C.vertices):
            i = P_inv[S]
            v = C.vertices[(j + 1) % M] - C.vertices[(j - 1) % M]
            ans[i] = f'{v.y} {-v.x}'

    return "\n".join(ans)

#==================================================
import sys
input=sys.stdin.readline
write=sys.stdout.write

T = int(input())
for _ in range(T):
    print(solve())
0