Official

F - Mod M Divisor Editorial by Nyaan Editorial for printing


まず、\(N^t\) の約数であることは \(t\) 個の \(N\) の約数の積で表せることと同値であることに注目します。すると、次の解法が浮かびます。

  • 次のグラフを考える。
    • \(0, 1, \dots, M-1\) を頂点とする。
    • \(N\) の各約数 \(d\) に対して \(r \to rd \bmod M\) に辺を張る。
  • このグラフ上で頂点 \(1 \bmod M\) から BFS をして得られる頂点 \(1 \bmod M\) からの距離が答えとなる。

しかしこの解法は \(N\) の約数の個数を \(d(N)\) としたとき \(\mathrm{O}(M d(N))\) の計算量が掛かり非常に低速です。

そこで約数を掛ける操作を素因数ごとに掛ける操作に分解します。

\(N\) が素数 \(p_0, p_1, \dots, p_{D-1}\) を用いて

\[N = \prod_{i=0}^{D-1} p_i^{a_i}\]

と素因数分解できたとします。このとき \(N\) の約数は

\[p_0^{e_0} p_1^{e_1} \cdots p_{D-1}^{e_{D-1}}, 0 \leq e_i \leq a_i\]

と表せます。言い換えると、約数を \(1\) 個掛ける操作は、次の \(D\) 段階に分けられます。

  • \(e_0 \in \lbrace 0, \dots, a_0 \rbrace\) を選び \(p_0^{e_0}\) を掛ける。
  • \(e_1 \in \lbrace 0, \dots, a_1 \rbrace\) を選び \(p_0^{e_1}\) を掛ける。
  • \(\vdots\)
  • \(e_{D-1}\in \lbrace 0, \dots, a_{D-1} \rbrace\) を選び \(p_{D-1}^{e_{D-1}}\) を掛ける。

この \(D\) 段階を \(1\) 周として考えて、最後の素因数を処理したら再び最初の素因数に戻ることにします。
この仕組みを用いましょう。グラフの頂点を \((j,r)\) で表します。これは、

  • 現在の積を \(M\) で割った余りが \(r\)
  • 次に処理する素因数が \(p_j\)

という状態です。そして、各 \(e=0,1,\dots,a_j\) に対して、

\[(j, r) \to ((j+1) \bmod d, r p_j^e \bmod M)\]

という辺を張ります。辺の長さについては次のように決めます。

  • \(j=0\) のときは、新しい \(D\) 段階の 1 周が始まるので辺重みを \(1\) とする。
  • \(j \neq 0\) のときは辺重みを \(0\) とする。

このグラフ上で初期状態 \((0, 1 \bmod M)\) から 01-BFS を行い、各 \(r\) に対して \(\min_j \mathrm{dist}(j, r)\) を求めれば答えを得られます。

計算量は \(N\) の重複付き素因数の個数が \(\mathrm{O}(\log N)\) で抑えられることから、素因数分解の計算量が \(\mathrm{O}(\sqrt{N})\) なのと合わせて \(\mathrm{O}(\sqrt{N} + M \log N)\) で問題を解くことができて、これは十分高速です。

  • 実装例 (PyPy または Codon)
    • 細かい解法の差異によっては、同じ計算量であっても PyPy では定数倍が非常に重く通らないことがあるようです。高速な処理系である Codon の使用を推奨します。
import collections
import sys

N, M = [int(x) for x in input().split()]
if N == 1:
    for m in range(M):
        print(1 if m == 1 % M else -1)
    sys.exit(0)

f = []
for i in range(2, 10**8):
    if i * i > N:
        break
    c = 0
    while N % i == 0:
        N //= i
        c += 1
    if c > 0:
        f.append((i % M, c))
if N != 1:
    f.append((N % M, 1))
    N = 1

inf = 10**18
d = [[inf] * M for i in range(len(f))]
Q = collections.deque()

d[0][1 % M] = 0
Q.appendleft((0, 1 % M, 0))
while len(Q):
    i, j, dij = Q.popleft()
    if d[i][j] != dij:
        continue
    ni = (i + 1) % len(f)
    nd = dij + (1 if i == 0 else 0)
    nj = j
    for e in range(f[i][1] + 1):
        if nd < d[ni][nj]:
            d[ni][nj] = nd
            if i == 0:
                Q.append((ni, nj, nd))
            else:
                Q.appendleft((ni, nj, nd))
        nj = nj * f[i][0] % M

ans = [inf] * M
for i in range(len(f)):
    for j in range(M):
        ans[j] = min(ans[j], max(1, d[i][j]))
for j in range(M):
    if ans[j] == inf:
        ans[j] = -1
print("\n".join(map(str, ans)))

posted:
last update: