AT274の技術だったりなかったり

あっとのTECH LOG

競プロのこととか技術のこととか。たまに日常を書きます。

ABC168 E - ∙ (Bullet)

問題原文

atcoder.jp

解法

実験とかすると、  (a, b) (a / gcd(a, b), b / gcd(a, b)) としていいことは分かる。(一旦0は無視で)

これをとりあえず集計したくなる。
集計の結果、例えば  (3, 5) S 個、  (3, 5) と仲が悪いものの個数が  T 個 ということがわかれば、
S からも Tからも選ばない S から1つ以上選ぶ T から 1つ以上選ぶ 通り数を足し合わせたものがそのキーに関しての通り数となって、答えが計算できそうということがわかる。

ただ、  (3, 5) と仲が悪いものは、  (-5, 3) であったり、  (5, -3) であったりしてめんどくさい。
これは、片方がマイナスのものはマイナスを  b の方に押し付けることで、まとめて集計できる。
(たとえば、  (-5, 3) (5, -3) として集計する)

また、  (-3, -5) のようなものは  (3, 5) のようにしても問題ないので、そのようにして集計する。

 (3, 0) のような片方0に関しては、 仲が悪いものは  (0, -10) のようなもう片方が0のものになる。
後々計算しやすくするように、  b が 0なら  (1, 0) として、  a が 0 なら  (0, -1) として集計しておく。

 (0, 0) は他の全てと仲が悪いので、「 (0, 0) から1つ選ぶ」という組合せを完全に別で計算する必要がある。
これは普通に  (0, 0) の数を数えておくことでできる。

実装

複雑ではないんだけどひ〜

from math import gcd
from collections import defaultdict
N = int(input())
MOD = 10 ** 9 + 7

zero_zero_num = 0
fishes = defaultdict(int)
for i in range(N):
    a, b = map(int, input().split())
    if a == 0 and b == 0:
        zero_zero_num += 1
    elif a == 0:
        fishes[(0, -1)] += 1
    elif b == 0:
        fishes[(1, 0)] += 1
    else:
        g = gcd(a, b)
        a, b = a // g, b // g
        if (a > 0 and b > 0) or (a < 0 and b < 0):
            fishes[(abs(a), abs(b))] += 1
        else:
            a, b = (-a, -b) if a < 0 else (a, b)
            fishes[(a, b)] += 1


ans = 1
keys = list(fishes.keys())
visited = set()
for (kai, kbi) in keys:
    if kbi >= 0:
        kaj, kbj = kbi, -kai
    else:
        kaj, kbj = -kbi, kai

    if ((kai, kbi) in visited) or ((kaj, kbj) in visited):
        continue

    ans *= (1 + (pow(2, fishes[(kai, kbi)], MOD) - 1) + (pow(2, fishes[(kaj, kbj)], MOD) - 1))
    ans %= MOD
    visited.add((kai, kbi))
    visited.add((kaj, kbj))

print((ans + zero_zero_num - 1) % MOD)

感想

キーを集計しやすいようにあらかじめ変形する みたいなテク?なのかな。。。
bにマイナスを押し付けるとこんなに楽になるんだなぁ。勉強になりました。