結果

問題 No.981 一般冪乗根
ユーザー hari64hari64
提出日時 2021-07-22 18:09:46
言語 PyPy3
(7.3.15)
結果
AC  
実行時間 3,642 ms / 6,000 ms
コード長 6,784 bytes
コンパイル時間 1,106 ms
コンパイル使用メモリ 82,604 KB
実行使用メモリ 214,184 KB
最終ジャッジ日時 2024-10-10 07:56:23
合計ジャッジ時間 177,435 ms
ジャッジサーバーID
(参考情報)
judge5 / judge3
このコードへのチャレンジ
(要ログイン)

テストケース

テストケース表示
入力 結果 実行時間
実行使用メモリ
testcase_00 AC 3,247 ms
202,188 KB
testcase_01 AC 3,276 ms
203,328 KB
testcase_02 AC 3,259 ms
201,116 KB
testcase_03 AC 3,304 ms
200,412 KB
testcase_04 AC 3,263 ms
196,620 KB
testcase_05 AC 200 ms
77,728 KB
testcase_06 AC 197 ms
77,432 KB
testcase_07 AC 200 ms
77,260 KB
testcase_08 AC 199 ms
77,592 KB
testcase_09 AC 191 ms
77,164 KB
testcase_10 AC 193 ms
77,384 KB
testcase_11 AC 193 ms
77,312 KB
testcase_12 AC 195 ms
77,384 KB
testcase_13 AC 191 ms
77,248 KB
testcase_14 AC 196 ms
77,656 KB
testcase_15 AC 194 ms
77,320 KB
testcase_16 AC 194 ms
77,296 KB
testcase_17 AC 205 ms
77,412 KB
testcase_18 AC 199 ms
77,768 KB
testcase_19 AC 190 ms
77,220 KB
testcase_20 AC 193 ms
77,372 KB
testcase_21 AC 193 ms
77,356 KB
testcase_22 AC 192 ms
77,508 KB
testcase_23 AC 201 ms
77,488 KB
testcase_24 AC 199 ms
77,612 KB
testcase_25 AC 3,193 ms
201,756 KB
testcase_26 AC 3,642 ms
214,184 KB
testcase_27 AC 104 ms
76,892 KB
testcase_28 AC 3,087 ms
193,456 KB
evil_60bit1.txt MLE -
evil_60bit2.txt TLE -
evil_60bit3.txt TLE -
evil_hack AC 262 ms
61,980 KB
evil_hard_random MLE -
evil_hard_safeprime.txt TLE -
evil_hard_tonelli0 TLE -
evil_hard_tonelli1 MLE -
evil_hard_tonelli2 MLE -
evil_hard_tonelli3 MLE -
evil_sefeprime1.txt MLE -
evil_sefeprime2.txt MLE -
evil_sefeprime3.txt TLE -
evil_tonelli1.txt MLE -
evil_tonelli2.txt MLE -
権限があれば一括ダウンロードができます

ソースコード

diff #

def is_prime(n: int) -> bool: #ミラー–ラビン素数判定法
    # https://qiita.com/srtk86/items/609737d50c9ef5f5dc59
    # https://en.wikipedia.org/wiki/Strong_pseudoprime
    assert isinstance(n, int) and 0 < n
    if n == 2:
        return True
    if n == 1 or n & 1 == 0:
        return False
    d = (n - 1) >> 1
    while d & 1 == 0:
        d >>= 1  # n-1=(2**s)*d (dは奇数)
    L=[a for a in (2, 325, 9375, 28178,450775, 9780504, 1795265022) if a<n]
    if n>=2**64:  # これはwikiによる
        from random import randint
        L += [randint(1, n-1) for _ in range(100)]
    for a in L:  # nが素数ならばa^d≡1 (mod p) もしくは
        t = d  # a^((2^r)*d)≡-1 (mod p) が成立すべき
        y = pow(a, t, n)
        while t != n - 1 and y != 1 and y != n - 1:
            y = (y * y) % n
            t <<= 1
        if y != n - 1 and t & 1 == 0:
            return False
    else:
        return True


def findFactorRho(n):
    from math import gcd
    m = 1 << n.bit_length() // 8
    for c in range(1, 99):
        def f(x):
            return (x * x + c) % n
        y, r, q, g = 2, 1, 1, 1
        while g == 1:
            x = y
            for i in range(r):
                y = f(y)
            k = 0
            while k < r and g == 1:
                ys = y
                for i in range(min(m, r - k)):
                    y = f(y)
                    q = q * abs(x - y) % n
                g = gcd(q, n)
                k += m
            r <<= 1
        if g == n:
            g = 1
            while g == 1:
                ys = f(ys)
                g = gcd(abs(x - ys), n)
        if g < n:
            if is_prime(g):
                return g
            elif is_prime(n // g):
                return n // g
            return findFactorRho(g)


def prime_factors(n, return_e: bool = False):
    i = 2
    ret = {}
    rhoFlg = 0
    while i*i <= n:
        k = 0
        while n % i == 0:
            n //= i
            k += 1
        if k:
            ret[i] = k
        i += 1 + i % 2
        if i == 101 and n >= 2 ** 20:
            while n > 1:
                if is_prime(n):
                    ret[n], n = 1, 1
                else:
                    rhoFlg = 1
                    j = findFactorRho(n)
                    k = 0
                    while n % j == 0:
                        n //= j
                        k += 1
                    ret[j] = k
    if n > 1:
        ret[n] = 1
    if rhoFlg:
        ret = {x: ret[x] for x in sorted(ret)}
    return list(ret.keys())


def primitive_root(p: int) -> int:
    """素数pに対する何らかの原始根rを一つ求める (rの位数はp-1)
    https://manabitimes.jp/math/842
    https://37zigen.com/primitive-root/ 最速ではない
    """
    if p == 2:
        return 1
    from random import randint
    p_1_pfs = prime_factors(p-1, return_e=False)
    cnt = 0
    while cnt < 2*p:
        cnt += 1
        cfr = randint(2, p-1)  # candidates for r
        for pf in p_1_pfs:
            if pow(cfr, (p-1)//pf, p) == 1:  # p-1//pfが位数候補
                break
        else:  # 全ての位数候補に対して、その否定がなされた。
            return cfr
    else:
        raise AssertionError


def discrete_logarithm(x, y, mod):
    """離散対数問題 x^k≡yとなるようなkを求める O(√mod)"""
    babys = {}
    sqrt = int(mod**0.5)+1
    # baby-step
    x_baby = 1
    for i in range(sqrt):
        babys[x_baby] = i  # x^(sqrt-1)まで求めておく
        if x_baby == y:
            return i
        x_baby = x_baby * x % mod
    # giant-step
    x_giant = pow(x_baby, mod-2, mod)  # x^(-sqrt)
    for i in range(1, sqrt+1):
        y = y * x_giant % mod
        if y in babys:  # ここで√modずつ見ていく為に速い
            return babys[y] + i*sqrt
    return -1  # 原始根でない限り、必ず存在するとは言えない


def _inv_gcd(a, b) -> tuple:
    """
    返り値は(gcd(a,b),x) (ただしxa≡g (mod b) 0<=x<b//g)
    計算量 O(log(max(a,b))) ラメの定理などからも分かる通り計算量が非常に小さい
    (0<=a<=b∊N len(str(a))=dの時、gcd算出の為の計算回数は5d以下)
    逆元を求めるならpow(g,-1,b)でもいいが、versionの問題とgとbが互いに素という制約はある
    また、このコードは拡張ユークリッドの互除法の行列表現からも理解可能 再帰でも書ける
    https://github.com/atcoder/ac-library/blob/master/atcoder/internal_math.hpp
    """
    # assert 0 <= a and 1 <= b
    a %= b
    if a == 0:
        return (b, 0)
    s = b  # m0*a≡s (mod b) これらの性質をm0,m1は満たしていることに注意
    t = a  # m1*a≡t (mod b)
    m0 = 0  # s*|m1|+t*|m0|<=b
    m1 = 1  # また、a<bよりt<sが成立
    while t:
        # この三行は互除法そのもの
        u = s // t
        s -= t * u
        s, t = t, s
        # m0とm1が先ほど挙げた性質を保持し続ける様に変更する oがoldm、nがnewを示すとして
        # o_m0*a=o_s o_m1*a=o_t (冒頭にあげた性質)
        # n_s=o_t n_t=o_s-o_t*u (互除法による変更)
        # o_m1*a=n_s (o_m0-o_m1*u)*a=n_t (代入して整理)
        # これを先の性質の式と見比べるとn_m0=o_m1 n_m1=o_m0-o_m1*uを得る これが下の式の正体
        # 三つ目の性質も保たれていることは代入すればすぐわかる
        m0 -= m1 * u
        m0, m1 = m1, m0
    if m0 < 0:  # 三つ目の性質を利用するとu*|n_m0|<=b//s=b//g⇒|n_mo|<b/g (∵u>=0)
        m0 += b // s
    return (s, m0)


def eea(a: int, b: int) -> tuple:
    """
    ax+by=gcd(a,b)なる(x,y)を求める(拡張されたユークリッドの互除法)
    (extended_euclidean_algorithm)(元のユークリッドの互除法はgcdを求める手法を指す)
    https://qiita.com/drken/items/b97ff231e43bce50199a
    https://ja.wikipedia.org/wiki/ユークリッドの互除法 <-英語版が優秀すぎ
    https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm
    また、以上の話題は連分数展開とも関連があるらしい
    """
    g, x = _inv_gcd(a, abs(b))
    y = (b//abs(b))*(g-a*x)//abs(b)
    assert (g-a*x) % abs(b) == 0
    return (x, y)


def main():
    import sys
    from math import gcd
    input = sys.stdin.buffer.readline
    T = int(input())
    for _ in range(T):
        p, k, a = map(int, input().split())
        g = primitive_root(p)
        y = discrete_logarithm(g, a, p)
        gkp = gcd(k, p-1)
        if y % gkp != 0:
            print(-1)
            continue
        z = (y//gkp)*eea(k//gkp, -(p-1)//gkp)[0]
        x = pow(g, z % (p-1), p)
        print(x)


if __name__ == '__main__':
    main()
0