問題
$T$ 個の問い合わせに答えよ。各問い合わせでは素数 $p$ と整数 $K,Y$ が与えられる。
$$x^K\equiv Y\pmod p,\qquad 0\le x<p$$
を満たす $x$ の個数と、解があればその1 つを出力せよ。解が無ければ個数 0 と $-1$ を出力せよ。解が複数あるときはどれを出力してもよい(入出力例の解は模範解答が出したもの)。
入力形式
T
K_1 Y_1 p_1
...
K_T Y_T p_T
制約
| # | 制約 |
|---|---|
| 1 | $1\le T\le300$ |
| 2 | $2\le p\le10^9$、$p$ は素数 |
| 3 | $1\le K\le10^9$ |
| 4 | $0\le Y<p$ |
入出力例
入力例1
6
4 2 7
3 5 7
2 0 5
12 9 13
5 1 11
1000000000 899862517 998244353出力例1
2 2
0 -1
1 0
0 -1
5 1
512 645654180(1 行目: $x^4\equiv2\pmod7$ の解は $x=2,5$ の 2 個。$\gcd(4,6)=2$ 個になる。3 行目: $Y=0$ の解は $x=0$ だけ。4 行目: $K=12=p-1$ なので、フェルマーの小定理で $x\ne0$ なら $x^{12}\equiv1$ となり、$9$ にはならない。6 行目: $p-1=2^{23}\cdot7\cdot17$、$\gcd(10^9,p-1)=2^9=512$ 個)
背景と出典
- Library Checker に「Kth Root (Mod)」という同じ形の問題があり、制約は $T\le300$、$p\le10^9$ だったと記憶している(問題文・制約・出力形式は未確認。本問は個数も出す自作の形にした)
- Adleman–Manders–Miller (1977) の方法は Tonelli–Shanks を $q$ 乗根に広げたものと記憶している(原典未確認)。本解はそれと同じ計算量 $O(t\sqrt q)$ の部分を、Sylow 部分群への射影と Pohlig–Hellman(1978、原典未確認)の形で書いた
- 本問の入力例は自作
概念図
ヒント(クリックで段階的に開示)
ヒント1(方向性)
$p$ の原始根 $g$ を使って $x=g^X$、$Y=g^L$ と書けば $KX\equiv L\pmod{p-1}$ という 1 次合同式になる。解があるのは $\gcd(K,p-1)\mid L$ のときで、そのとき解は $\gcd(K,p-1)$ 個。$L$ を知らなくても、判定は $Y^{(p-1)/\gcd(K,p-1)}\equiv1$ でできる。
ヒント2(アプローチ)
$L$ を直接求める($O(\sqrt p)$ の BSGS)と、$p=998244353$ のように $p-1$ が小さい素因数ばかりのときに無駄が大きい。$p-1=\prod q^{t_q}$ と分解し、$Y$ を各 $q$ の部分群(位数 $q^{t_q}$)へ射影して、部分群ごとに解く。$q\nmid K$ の部分群では $K$ に逆元があるので $x_q=Y_q^{K^{-1}\bmod q^{t}}$ で終わり。
ヒント3(誘導)
- 射影: $e_q=\dfrac{p-1}{q^t}\cdot\Bigl(\bigl(\tfrac{p-1}{q^t}\bigr)^{-1}\bmod q^t\Bigr)$ とすると $Y_q=Y^{e_q}$。$\prod_qY_q=Y$
- $q\mid K$ の部分群: 生成元 $h=z^{(p-1)/q^t}$($z$ は $z^{(p-1)/q}\ne1$ となる数)で $Y_q=h^{L}$ の $L$ を桁ごとに求める(各桁は位数 $q$ の BSGS)。$K=q^eK'$ なら $x_q=h^{(L/q^e)\cdot K'^{-1}\bmod q^{t-e}}$
- $x=\prod_qx_q$
模範解答 (Python)
模範解答を表示する(まず自力で解いてから)
import sys
from math import gcd, isqrt
def factor_small(n):
"""n <= 10^9 程度を試し割りで素因数分解"""
fs = {}
d = 2
while d * d <= n:
while n % d == 0:
fs[d] = fs.get(d, 0) + 1
n //= d
d += 1 if d == 2 else 2
if n > 1:
fs[n] = fs.get(n, 0) + 1
return fs
def dlog_prime_order(h, a, q, p):
"""h の位数が素数 q のとき h^L = a となる L (0<=L<q) を BSGS で"""
m = isqrt(q - 1) + 1
table = {}
e = 1
for j in range(m):
if e not in table:
table[e] = j
e = e * h % p
step = pow(h, q - m, p) # h^(-m)
g = a
for i in range(m):
j = table.get(g)
if j is not None:
return i * m + j
g = g * step % p
raise ValueError("no log")
def dlog_sylow(h, a, q, t, p):
"""h の位数が q^t、a が <h> に入るとき h^L = a の L を Pohlig–Hellman で桁ごとに"""
gamma = pow(h, q ** (t - 1), p) # 位数 q
L = 0
qk = 1
hinv = pow(h, p - 2, p)
for k in range(t):
# (h^-L * a)^(q^(t-1-k)) = gamma^(d_k)
cur = pow(pow(hinv, L, p) * a % p, q ** (t - 1 - k), p)
d = dlog_prime_order(gamma, cur, q, p) if q > 1 else 0
L += d * qk
qk *= q
return L
def kth_root(k, y, p, fs):
"""x^k ≡ y (mod p) の解の個数と解を 1 つ。k >= 1"""
y %= p
if y == 0:
return 1, 0
n = p - 1
g = gcd(k, n)
if pow(y, n // g, p) != 1:
return 0, -1
x = 1
for q, t in fs.items():
qt = q ** t
rest = n // qt
# y の q 成分 y_q = y^(rest * inv(rest) mod qt):位数 q^t の部分群へ射影
eps = rest * pow(rest, -1, qt) % n
yq = pow(y, eps, p)
e = 0
kk = k
while kk % q == 0 and e < t:
kk //= q
e += 1
if e == 0:
xq = pow(yq, pow(k % qt, -1, qt), p)
else:
# 位数 q^t の生成元 h = z^rest(z は q 乗非剰余)
z = 2
while pow(z, n // q, p) == 1:
z += 1
h = pow(z, rest, p)
L = dlog_sylow(h, yq, q, t, p) # q^e | L は剰余判定で保証
mod = q ** (t - e)
M = (L // q ** e) * pow(kk % mod, -1, mod) % mod if mod > 1 else 0
xq = pow(h, M, p)
x = x * xq % p
return g, x
def main():
data = sys.stdin.buffer.read().split()
T = int(data[0])
out = []
pos = 1
cache = {}
for _ in range(T):
k = int(data[pos]); y = int(data[pos + 1]); p = int(data[pos + 2]); pos += 3
if p not in cache:
cache[p] = factor_small(p - 1)
c, x = kth_root(k, y, p, cache[p])
out.append(f"{c} {x}")
sys.stdout.write("\n".join(out) + "\n")
main()
計算量: $p-1$ の試し割り $O(\sqrt p)$、問い合わせ 1 回は $q\mid\gcd(K,p-1)$ の素因数ごとに $O(t_q\sqrt q)$ の BSGS。最悪は $p=2q+1$($q$ が素数)で $K$ が $q$ の倍数のとき、$\sqrt q\approx2.2\times10^4$。実測(Python 3.14、Apple M5 Pro、起動込み、$T=300$)で、その最悪形 0.60 s、$p=998244353$・$K=2^{23}\cdot r$ が 0.02 s、乱択 0.03 s。
Step-by-Step 解説
1解の個数と存在判定
原始根 $g$ で $x=g^X$、$Y=g^L$ とすると $x^K=Y\iff KX\equiv L\pmod{p-1}$。$d=\gcd(K,p-1)$ として、解があるのは $d\mid L$ のときで、$X$ は法 $p-1$ で $d$ 個。$d\mid L\iff Y^{(p-1)/d}=g^{L(p-1)/d}=1$ なので、離散対数を求めずに判定できる。$Y=0$ なら $x=0$ の 1 個($K\ge1$)。
2部分群に分ける
位数 $n=p-1$ の巡回群は、$n=\prod q^{t_q}$ に対して「位数 $q^{t_q}$ の部分群」の直積になる。$Y$ の $q$ 成分は $Y_q=Y^{e_q}$($e_q\equiv1\pmod{q^t}$、$e_q\equiv0\pmod{n/q^t}$)で、$\sum_qe_q\equiv1\pmod n$ だから $\prod_qY_q=Y$。各成分で $x_q^K=Y_q$ を解いて掛ければ $x^K=Y$。
3$q\nmid K$ の成分は逆元 1 回
位数 $q^t$ の群では $K$ が $q$ と互いに素なら $K$ 乗が全単射で、逆写像は $K^{-1}\bmod q^t$ 乗。$p-1$ と $K$ が互いに素なら全部の成分がこれで、$x=Y^{K^{-1}\bmod(p-1)}$ と同じ。
4$q\mid K$ の成分は離散対数
生成元 $h$ をとり $Y_q=h^L$ の $L$($0\le L<q^t$)を求める。$\gamma=h^{q^{t-1}}$(位数 $q$)とすると、$L$ の $q$ 進の $k$ 桁目は $(h^{-L_{<k}}Y_q)^{q^{t-1-k}}=\gamma^{d_k}$ から位数 $q$ の BSGS で決まる(Pohlig–Hellman)。$K=q^eK'$($e\le t$)なら $q^e\mid L$(Step 1 の判定を通っているから)で、$M=(L/q^e)\cdot K'^{-1}\bmod q^{t-e}$、$x_q=h^M$ とすれば $x_q^K=h^{q^eK'M}=h^L$。
5「素因数ごとに順に根を取る」の落とし穴
$K=4$ なら「平方根を 2 回」と考えたくなる。しかし 1 回目の平方根は 2 つあり、選び方によっては 2 回目が取れない。入力例 1 の 1 行目 $x^4\equiv2\pmod7$ では、$2$ の平方根は $3$ と $4$。$4$ を選べば $4$ の平方根 $2,5$ が答えになるが、$3$ を選ぶと $3$ は平方非剰余で行き詰まる。途中の根を $q$ 乗根全体から一様に選ぶ実装は、解のある 1242041 問い合わせのうち 96404 で失敗した(実測)。本解の射影の方法は「$q$ と互いに素な成分は $q$ 乗の逆写像で一意に戻す」ので、たまたまこの失敗を起こさない(本解の根を使って順に取ると 0 件の失敗、実測)。
迷った他案と採らなかった理由
| 案 | 1 問の計算量 | 採否 | 理由 |
|---|---|---|---|
| 全 $x$ を試す | $O(p)$ | 検算用のみ | $p<260$ の全問い合わせの検算に使った |
| $\gcd(K,p-1)=1$ のときだけ逆元、それ以外は解なし | $O(\log p)$ | 不採用(誤り) | 解のある 1242041 問い合わせ中 430513 で誤り(実測) |
| 原始根で $Y$ の離散対数を直接(BSGS、位数 $p-1$) | $O(\sqrt p)$ | 不採用 | 正しい(3 種 900 問い合わせで本解と一致)。$p=998244353$ で 300 問 1.33 s 対本解 0.02 s、乱択 0.81 s 対 0.03 s。最悪形は 1.26 s 対 0.60 s |
| 素因数ごとに順に根を取り、途中の根を任意に選ぶ | $O(\sum t\sqrt q)$ | 不採用(誤り) | Step 5。96404/1242041 で失敗 |
| Adleman–Manders–Miller(Tonelli–Shanks の一般化) | $O(t\sqrt q)$ | 不採用(未実装) | 本解と同じ計算量。射影の方が「成分ごとに独立」で正しさを説明しやすいと判断 |
| 部分群への射影+Pohlig–Hellman(本解) | $O(\sum_{q\mid d}t_q\sqrt q)$ | 採用 | 最悪 0.60 s |
前提が変わったら
- $p$ が $10^{18}$ までなら、$p-1$ の分解に Pollard ρ(Day179-Q5)が要る。さらに $q$ が $10^{17}$ 級だと $\sqrt q$ の BSGS が間に合わないので、$K$ の大きな素因数が $p-1$ を割る入力は解けない(Pollard ρ 型の離散対数でも $\sqrt q$ は変わらない)
- 合成数の法なら、素数べきごとに Hensel で持ち上げて CRT(Day176-Q5 の平方根の一般化)。$p=2$ のべきは別扱い
- 解を全部列挙するなら、1 つの解に $1$ の $d$ 乗根 $\zeta^i$ を掛ければよい($d$ が大きいと出力が大きい)
よくあるミス
| ミス | 原因 | 正しい書き方 |
|---|---|---|
| $Y=0$ を個数 $\gcd$ にする | 乗法群の話を 0 にも当てはめる | $Y=0$ なら $x=0$ の 1 個 |
| 存在判定に $Y^{(p-1)/K}$ を使う | $K\mid p-1$ と思い込む | $\gcd(K,p-1)$ で割る |
| 素因数ごとに順に根を取る | $K=q_1q_2$ を「$q_1$ 乗根の $q_2$ 乗根」と考える | 途中の根の選び方で失敗する(Step 5、入力例 1 の 1 行目) |
| $q\mid K$ の成分で $e>t$ のときに逆元を取ろうとする | $K$ の $q$ の指数が $p-1$ の指数を超える | $e$ を $t$ で打ち切る。$e=t$ なら $x_q=1$ |
| BSGS の表を位数 $p-1$ で作る | 部分群に分けない | 位数 $q$ の部分群で作れば表は $\sqrt q$ |
次のステップ
- 発展1: Adleman–Manders–Miller を書いて、本解と同じ入力で速さを比べる
- 発展2: 法 $m\le10^{18}$(合成数)の $K$ 乗根の個数(Day176-Q5 の $K$ 一般化。素数べきでの付値と Hensel)
- 発展3: $p\le10^{18}$ で、$K$ と $p-1$ の共通素因数が小さいときだけ解く版(Pollard ρ+本解)
検証ログ(本セッションで実行した数値)
- 全探索との一致(実測): $p<260$ の素数 55 個すべて、$K=1..2p+1$ と乱数の $K$($\le10^9$)20 個、すべての $Y$ の 2256750 問い合わせで、個数・解の正しさ・解なしの判定が全探索と全件一致
- 誤った方法(実測、$p<260$ の奇素数、$K=1..2p+1$、$Y\ne0$ の 2117254 問い合わせ、うち解あり 1242041): 互いに素のときだけ逆元 430513、途中の根をランダムに選んで順に取る 96404 で誤り(最初の例は $p=7,K=4,Y=2$)。本解の根で順に取ると 0
- 別解との照合(実測): 原始根の離散対数を直接求める方法と、最悪形・$p=998244353$・乱択の各 300 問い合わせで個数が一致し、両方の解が $x^K\equiv Y$ を満たした
- 時間(実測、起動込み、$T=300$): $p=2q+1$($q$ 素数、$p<10^9$)・$K$ が $q$ の倍数 0.60 s・25 MB、$p=998244353$・$K=2^{23}r$ 0.02 s、乱択 0.03 s。直接の離散対数はそれぞれ 1.26 s・1.33 s・0.81 s
- 扱っていない範囲: Library Checker の原文の確認、Adleman–Manders–Miller の実装、合成数の法