Official

E - Most Brilliant Starry Sky Editorial by harurun4635 Editorial for printing


求めたいものは「星を原点の周りに反時計回りに並べた時、角度の幅が \(180^\circ\) 未満となる連続した星の個数の最大値」です。

(もちろん時計回りでもよいです。また、今回の制約では \(180^\circ\) 丁度はありませんから「以下」としても良いです)


星を原点のまわりに反時計回りに並べる

いわゆる偏角ソートと呼ばれるアルゴリズムです。

簡単な方法として、atan2 などを用いて角度を求め、浮動小数点数で比較することが考えられます。しかし、制約によっては浮動小数点数の誤差が原因で WA となる可能性があります。そこで、今回は整数演算だけで比較する方法を紹介します。

偏角ソートは、外積を用いることで実現できます。(一般には三次元ベクトルに対して定義されますが、ここでは、点 \(a, b\) について \(a \times b = a_x \times b_y - a_y \times b_x\) と定義しましょう)ソートを行うには、任意の二要素の大小を比較できれば十分です。そこで、二つの点 \(a,b\) の偏角を比較する方法を考えましょう。

まず、すべての点の偏角が \([0^\circ,180^\circ)\) の範囲にある場合を考えます。このとき、二つの点の外積 \(a\times b\) を求めれば、次のように比較できます。(二つの点の偏角の差が \(180^\circ\) 未満であれば、外積の符号から偏角の大小を正しく判定できます)

  • \(a\times b \gt 0\) ならば、\(a \lt b\)
  • \(a\times b=0\) ならば、\(a=b\)
  • \(a\times b \lt 0\) ならば、\(a \gt b\)

偏角が \([0^\circ,360^\circ)\) の範囲にある場合、二つの点の偏角の差が \(180^\circ\) を超えることがあるため、外積の符号だけでは正しく比較できません。そこで、点を偏角が \([0^\circ,180^\circ)\) のものと \([180^\circ,360^\circ)\) のものに分けます。

これにより、浮動小数点数を使わずに偏角ソートを実現できます。

角度の幅が \(180^\circ\) 未満となる連続した星の個数の最大値

ソート後であれば、「角度の幅が \(180^\circ\) 未満となる連続した星」は(円環における)区間となっているはずです。

よって、尺取法を考えましょう。点 \(P_l\) を区間の左端とした時、この偏角を \(x\) として「 \([x : x + 180^\circ)\) の区間に点 \(P_r\) の偏角が含まれるかどうか」が判定したいです。制約から丁度 \(x +180^\circ\) が存在しないことに注意すると、これは \(P_l \times P_r \ge 0\) などとして判定することができます。

円環を切り開いて \(2\) 倍の長さにしておくと、簡単に実装できるでしょう。

計算量は \(O(N\log N)\) です。


実装例 (CPython)

from functools import cmp_to_key

def solve():
    n = int(input())
    p = [tuple(map(int, input().split())) for _ in range(n)]

    def cmp(a, b):
        x, y = a
        u, v = b
        h = y < 0 or (y == 0 and x < 0)
        k = v < 0 or (v == 0 and u < 0)
        if h != k:
            return h - k
        z = x * v - y * u
        return (z < 0) - (z > 0)

    p.sort(key=cmp_to_key(cmp))
    p *= 2

    ans = r = 1
    for l in range(n):
        r = max(r, l + 1)
        x, y = p[l]
        while r < l + n:
            u, v = p[r]
            if x * v <= y * u:
                break
            r += 1
        ans = max(ans, r - l)

    print(ans)

for _ in range(int(input())):
    solve()

posted:
last update: