DISCO presents ディスカバリーチャンネル コードコンテスト2016 予選 C - ロト2
思考過程
与えられた整数のそれぞれについて,他の整数のそれぞれと積を取り,の倍数であるか確認する解法がまず思いつきます. が,計算量がO()になり,今回の入力サイズはとなるのでTLEしてしまいます.
ここでサンプル1を見てみましょう.
4 6 1 3 2 6
積を考えたいので,それぞれの要素に何をかければの倍数になるか考えます. すると,にはをかければいいことに気づきます. は,の約数です.必要そうなので,ここら辺での約数をとりあえず列挙しておきます. では前処理として,約数ごとに番目以降のその約数の個数を計算して,配列に保存しておけばいいのではないでしょうか. これができたら,を順にみていって,かけたらいいの個数がで求まり,答えがわかります. 前処理をした配列を値とし,対応する約数をキーとした辞書を持っておけば実装できそうです! しかし残念ながらこれも前処理にかかり,今回の入力だとTLEしてしまいます….
解法
の約数について考えるのはいいことでした.さらにの約数なら高々1500個くらいで抑えられるので1,ならしていいです.ここで,はi番目のの約数とします.そして,をとの最大公約数として持つAの要素の個数を,ごとに数えておきます2.
以降,をとの最大公約数として持つAの要素の個数のの個数と単に言います. との個数の積を取ると,との積を因数に持つペアの個数だということを発想します.ここは例を考えるとわかりやすくて,サンプルを考えましょう.サンプル1から要素を1つ増やしたものです.
5 6 1 3 2 6 4
このとき,6の約数は1, 2, 3, 6であり,1の個数は1個,2の個数は2個,3の個数は1個,6の個数は1個です.1の個数と6の個数をかけるとを含むペアは1通りです(1と6).2の個数と3の個数をかけると,を含むペアは2通りです(3と2,3と4).1の個数と4の個数をかけるとを含むペアは1通りです(1と4).を含むペアは1通りです(2と6).を含むペアと,を含むペアというのは区別します3.
これに対し,とをかけての約数になるようなペアだけ数え上げればいいです.具体的には,約数を二重ループで回して,順番を気にせずにかけての約数になるなら和を取ってしまいます.そうするとを含むペアとを含むペアが同じなので,最終的な和は答えより2倍大きい値になります.ちょうど2倍になっているので2で割ってあげると求めたい答えが得られます.
ただし,一つ注意する点があります.6の個数と6の個数をかけるとを含むペアの総数が得られるとしたいですが,同じものを2つ選んだペアも数え上げてしまっているので,答えが正しいものより大きくなってしまします(実際,上のサンプルでは6の個数は1個でペアはできないのに,を含むペアを一つ数えてあげてしまうことになります).これを避けるために,とが同じものだったときは,を足してあげればいいです.これはという集合から2つ選ぶという組合せの総数と考えることができます4.
提出コード
提出コード
from math import gcd
import sys
input = sys.stdin.buffer.readline
def main():
N, K = (int(i) for i in input().split())
A = [int(i) for i in input().split()]
def enum_divisors(n):
# 約数列挙
divs = []
for i in range(1, n+1):
if i*i > n:
break
if n % i == 0:
divs.append(i)
if n//i != i:
# i が平方数でない
divs.append(n//i)
return divs
divs = enum_divisors(K)
G = [gcd(a, K) for a in A]
B = {d: 0 for d in divs}
for g in G:
B[g] += 1
ans = 0
for g in divs:
for rg in divs:
if (g*rg) % K == 0:
ans += B[g] * (B[rg] - (g == rg))
print(ans//2)
if __name__ == '__main__':
main()
僕はこの問題を解説ACしたのですが,解説pdfを読んでも全然わからなかったのでTwitterで質問しまくりました.はてぃーぽったーさん,茶碗蒸しさんありがとうございます.