본문으로 건너뛰기
김신건의 로그

이산 k 제곱근 (Discrete Kth Root)

· 수정 · 📖 약 3분 · 909자/단어 #algorithm #math #discrete-kth-root #number-theory #modular-arithmetic
discrete kth root, discrete root, 이산 k 제곱근, 이산 근

정의

이산 k 제곱근 (Discrete Kth Root) 은 소수 p 에 대해 x^k ≡ a (mod p) 를 만족하는 모든 x 를 찾는 문제. 원시근 (primitive root) 으로 지표화하여 이산 로그 문제로 환원한 뒤 BSGS 와 확장 유클리드 호제법으로 해결. O(√p log p + Ans·log p).

문제 상황과 동기

x^k ≡ a (mod p) 의 모든 해를 구하라. p 는 소수, k ≥ 2.

  • naive: x = 0..p-1 순회 + 거듭제곱. O(p log k). p=10^9 면 불가능.
  • 원시근 + BSGS: O(√p log p) 로 하나의 해 탐색 + 모든 해 나열.

핵심 통찰: 군 (Z/pZ)^ 는 순환군. 원시근 g 로 모든 원소를 g^y 로 표현하면 x^k ≡ a 는 k·y ≡ ind_g(a) (mod φ(p)) 로 변환.*

시각화

핵심 아이디어

원시근 g 로 지표화하여 이산 로그 문제로 변환: g^(k·y) ≡ g^b → k·y ≡ b (mod p-1).

  1. 원시근 g 탐색 (위수 = p-1).
  2. a 의 지표 b 를 BSGS 로: g^b ≡ a.
  3. x = g^y → 선형 합동식 k·y ≡ b (mod p-1).
  4. 확장 유클리드로 모든 y 탐색. 각 y → x = g^y mod p.

모든 해는 x = g^{y_0 + i·Δ} 꼴, Δ = (p-1) / gcd(k, p-1).

k·y ≡ b (mod p-1) 의 해:
  d = gcd(k, p-1)
  if b % d != 0: 해 없음
  k' = k/d, m' = (p-1)/d, b' = b/d
  y_0 = (b' · k'^{-1}) mod m'
  Δ = m'
  y_i = y_0 + i·Δ, i = 0..d-1

알고리즘

discrete_kth_root(p, k, a):
    if a == 0: return [0]

    g = find_primitive_root(p)
    b = bsgs(g, a, p)              # g^b ≡ a

    d = gcd(k, p - 1)
    if b % d != 0: return []

    m = (p - 1) / d
    k_inv = modular_inverse(k / d, m)
    y0 = (b / d) * k_inv % m

    solutions = []
    for i = 0..d-1:
        y = y0 + i * m
        solutions.append(g^y % p)

    sort(solutions)
    return solutions

구현

// Discrete Kth Root: x^k = a (mod p)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;

ll modpow(ll a, ll b, ll m) {
  ll r = 1;
  while (b) { if (b & 1) r = r * a % m; a = a * a % m; b >>= 1; }
  return r;
}

ll primitive_root(ll p) {
  vector<ll> pf;
  ll tmp = p - 1;
  for (ll i = 2; i * i <= tmp; i++)
      if (tmp % i == 0) { pf.push_back(i); while (tmp % i == 0) tmp /= i; }
  if (tmp > 1) pf.push_back(tmp);
  for (ll g = 2; g < p; g++) {
      bool ok = 1;
      for (ll f : pf) if (modpow(g, (p - 1) / f, p) == 1) { ok = 0; break; }
      if (ok) return g;
  }
  return -1;
}

ll bsgs(ll a, ll b, ll p) {
  if (b == 1) return 0;
  ll n = sqrt(p) + 1;
  unordered_map<ll, ll> baby;
  ll cur = 1;
  for (ll j = 0; j < n; j++) {
      if (!baby.count(cur)) baby[cur] = j;
      cur = cur * a % p;
  }
  ll factor = modpow(a, p - 1 - (n - 1), p);
  cur = b;
  for (ll i = 0; i < n; i++) {
      if (baby.count(cur)) return i * n + baby[cur];
      cur = cur * factor % p;
  }
  return -1;
}

ll modinv(ll a, ll m) { return modpow(a, m - 1, m); }

ll gcd(ll a, ll b) { return b ? gcd(b, a % b) : a; }

int main() {
  ll p, k, a; cin >> p >> k >> a;
  if (a == 0) { cout << "1\n0"; return 0; }
  ll g = primitive_root(p), b = bsgs(g, a, p), d = gcd(k, p - 1);
  if (b % d != 0) { cout << "0"; return 0; }
  ll m = (p - 1) / d;
  ll y0 = (b / d) * modinv(k / d, m) % m;
  vector<ll> ans;
  for (ll i = 0; i < d; i++) ans.push_back(modpow(g, y0 + i * m, p));
  sort(ans.begin(), ans.end());
  cout << ans.size() << "\n";
  for (ll x : ans) cout << x << " ";
  return 0;
}
stdin
11 3 8
결과
1
2

복잡도

항목
원시근 탐색O(p^(1/4) · log^2 p)
BSGS (하나의 해)O(√p log p)
모든 해 나열O(Ans · log p)
공간O(√p) (BSGS 테이블)

변형

  • CRT 결합: 합성수 mod 에서 각 소인수 해를 결합.
  • Pohlig-Hellman: p-1 인수가 작을 때 BSGS 보다 빠름.

함정

1. 원시근

p 가 소수이면 항상 존재. p-1 의 소인수분해 필요.

2. a = 0

유일한 해 x = 0. 별도 분기.

3. 해의 개수

gcd(k, p-1) 개 또는 0. d 가 클수록 해가 많음.

4. k 가 p-1 배수

a=1 이면 모든 x≠0 이 해, a≠1 이면 해 없음.

BOJ 연습 문제

번호제목정답률링크
BOJ 4357이산 로그49.4%kokoa-lab
BOJ 17603Factorization25.0%kokoa-lab

참고

이 글의 용어 (4개)
이산 로그 (Discrete Logarithm)algorithm
정의 이산 로그 (Discrete Logarithm) 는 군 G 에서 를 만족하는 최소 음이 아닌 정수 x 를 찾는 문제. 실수 로그와 달리, mod 연산 아래에서의 역연산은 어…
이산 제곱근 (Discrete Square Root / Tonelli-Shanks)algorithm
정의 이산 제곱근 (Discrete Square Root) 은 소수 p 에 대해 를 만족하는 x 를 찾는 문제. 가 이차잉여 (quadratic residue) 이면 해가 존재하…
중국인의 나머지 정리 (Chinese Remainder Theorem)algorithm
정의 중국인의 나머지 정리 (CRT) 는 다음과 같은 연립 합동식의 해가 유일 하게 존재함을 보장: 단, m1, m2, ..., mk 는 쌍마다 서로소 (pairwise copr…
확장 유클리드 호제법 (Extended Euclidean Algorithm)algorithm
정의 확장 유클리드 호제법 (Extended Euclidean Algorithm) 은 두 정수 a, b 에 대해 를 구하면서, 동시에 를 만족하는 정수 x, y 를 찾는 알고리즘…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기