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

Carmichael Function λ(n)

· 수정 · 📖 약 4분 · 1,186자/단어 #algorithm #math #number-theory #carmichael #rsa
Carmichael Function, 카마이클 함수, reduced totient, lambda function, 람다 함수, universal exponent

정의

Carmichael function λ(n) 은 gcd(a, n) = 1 인 모든 a 에 대해 a^m ≡ 1 (mod n) 을 만족하는 최소 양의 정수 m 입니다.

λ(n) = min{ m > 0 : a^m ≡ 1 (mod n) for all a with gcd(a, n) = 1 }

핵심 성질: 항상 λ(n) | φ(n). 오일러 피 함수 φ(n) 은 항상 유효하지만 tight 하지 않을 수 있습니다.

예시:

  • λ(1) = 1
  • λ(5) = 4 = φ(5) (소수이므로 같음)
  • λ(8) = 2, φ(8) = 4 (λ < φ)
  • λ(12) = 2, φ(12) = 4 (λ < φ)

계산법

소수 거듭제곱에서의 값

경우λ 값
λ(1)1
λ(2)1
λ(4)2
λ(2^k), k >= 32^(k-2)
λ(p^k), p 홀수 소수p^(k-1) * (p - 1)

2^k 에서 k >= 3 이면 λ(2^k) = 2^(k-2) 인 이유: 2^k 의 원시근이 존재하지 않고, 최대 위수가 2^(k-2) 이기 때문입니다.

일반 n 에서의 값

n = p1^a1 * p2^a2 * … * pk^ak 이면:

λ(n) = lcm(λ(p1^a1), λ(p2^a2), ..., λ(pk^ak))

예: λ(60) = λ(2^2 * 3 * 5) = lcm(λ(4), λ(3), λ(5)) = lcm(2, 2, 4) = 4

시각화

λ(n) 계산 과정 (n = 120 = 2^3 * 3 * 5):

flowchart TD
    N["lambda(120)"]
    N -->|"120 = 2^3 * 3 * 5"| D["소인수분해"]
    D --> A["lambda(8) = 2^(3-2) = 2"]
    D --> B["lambda(3) = 3-1 = 2"]
    D --> C["lambda(5) = 5-1 = 4"]
    A --> R["lambda(120) = lcm(2, 2, 4) = 4"]
    B --> R
    C --> R

φ(n) 과 λ(n) 의 관계 비교:

flowchart LR
    subgraph "phi vs lambda"
        P["phi(120) = 32"]
        L["lambda(120) = 4"]
        P -->|"lambda divides phi"| L
        L -->|"4 divides 32"| OK["OK"]
    end

φ(n) 과의 관계

λ(n) 은 항상 φ(n) 의 약수입니다:

λ(n) | φ(n)
  • φ(n) 은 a^φ(n) ≡ 1 (mod n) 을 보장하는 지수 (오일러 정리)
  • λ(n) 은 그 중 최소 지수
  • λ(n) < φ(n) 이면 φ(n) 을 지수로 쓰는 것은 낭비

실용적 의미: RSA 에서 개인 키 지수 d 를 계산할 때 φ(n) 대신 λ(n) 을 쓰면 d 가 더 작아져 복호화가 빠릅니다.

카마이클 수 (Carmichael Number)

카마이클 수 는 합성수 n 이지만 모든 gcd(a, n) = 1 인 a 에 대해 a^(n-1) ≡ 1 (mod n) 을 만족하는 수입니다.

조건: λ(n) | (n - 1) 이고 n 이 합성수.

가장 작은 카마이클 수: 561 = 3 * 11 * 17

  • λ(561) = lcm(2, 10, 16) = 80
  • 80 | 560 (= 561 - 1) 이므로 카마이클 수

IMPORTANT

카마이클 수는 페르마 소정리 기반 소수 판별을 속입니다. 밀러-라빈 소수 판별 을 사용해야 합니다.

알고리즘

단일 n 에 대한 λ(n)

소인수분해 후 각 소수 거듭제곱의 λ 를 lcm 으로 합칩니다. O(sqrt(n)).

carmichael(n):
    result = 1
    for each prime power p^k dividing n:
        if p == 2 and k >= 3:
            lam = 2^(k-2)
        else:
            lam = p^(k-1) * (p - 1)
        result = lcm(result, lam)
    return result

1 부터 N 까지 일괄 계산

에라토스테네스의 체 변형으로 O(N log log N) 에 계산합니다.

carmichael_sieve(N):
    lam[1] = 1
    for i in 2..N:
        lam[i] = i - 1   // 소수 가정으로 초기화
    for p in 2..N:
        if lam[p] == p - 1:   // p 가 소수
            for j in p, 2p, 3p, ..., N:
                // j 에서 p 의 기여를 lcm 으로 반영
                lam[j] = lcm(lam[j], p - 1)
    // 소수 거듭제곱 처리는 별도 필요
    return lam

실제 구현은 소인수분해 기반이 더 간단합니다.

구현

// Carmichael Function lambda(n)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

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

// lambda(p^k) 계산
ll lambda_pk(ll p, int k) {
  if (p == 2) {
      if (k == 1) return 1;
      if (k == 2) return 2;
      ll pk2 = 1;
      for (int i = 0; i < k - 2; i++) pk2 *= 2;
      return pk2;  // 2^(k-2)
  }
  // 홀수 소수: p^(k-1) * (p-1)
  ll pk1 = 1;
  for (int i = 0; i < k - 1; i++) pk1 *= p;
  return pk1 * (p - 1);
}

ll carmichael(ll n) {
  ll result = 1;
  for (ll p = 2; p * p <= n; p++) {
      if (n % p == 0) {
          int k = 0;
          while (n % p == 0) { n /= p; k++; }
          result = lcm(result, lambda_pk(p, k));
      }
  }
  if (n > 1) result = lcm(result, n - 1);  // n 이 소수
  return result;
}

// 검증: gcd(a, n) = 1 인 모든 a 에 대해 a^lambda(n) ≡ 1 (mod n)
ll power(ll base, ll exp, ll mod) {
  ll result = 1;
  base %= mod;
  while (exp > 0) {
      if (exp & 1) result = result * base % mod;
      base = base * base % mod;
      exp >>= 1;
  }
  return result;
}

int main() {
  ios::sync_with_stdio(0); cin.tie(0);
  int t; cin >> t;
  while (t--) {
      ll n; cin >> n;
      ll lam = carmichael(n);
      cout << "lambda(" << n << ") = " << lam << "\n";
  }
}
stdin
5
1
8
12
60
120
결과
lambda(1) = 1
lambda(8) = 2
lambda(12) = 2
lambda(60) = 4
lambda(120) = 4

복잡도

항목
단일 λ(n)O(sqrt(n))
공간O(1)

RSA 와의 관계

RSA 에서 n = p * q (p, q 는 큰 소수) 일 때:

  • φ(n) = (p-1)(q-1)
  • λ(n) = lcm(p-1, q-1)

개인 키 지수 d 는 e * d ≡ 1 (mod λ(n)) 으로 계산합니다. 원래 RSA 는 φ(n) 을 사용했지만, λ(n) 을 쓰면 d 가 더 작아져 복호화가 빠릅니다. 두 방법 모두 올바른 복호화를 보장합니다 (λ(n) | φ(n) 이므로).

함정

WARNING

구현 시 자주 발생하는 실수들.

1. 2^k 의 특수 처리

p = 2, k >= 3 이면 λ(2^k) = 2^(k-2) 입니다. 일반 공식 p^(k-1)(p-1) = 2^(k-1) 을 쓰면 틀립니다. 2^k 에서는 원시근이 존재하지 않기 때문입니다.

2. lcm 오버플로우

lcm(a, b) = a / gcd(a, b) * b 에서 나눗셈을 먼저 해야 합니다. a * b / gcd(a, b) 로 하면 중간 값이 오버플로우할 수 있습니다.

3. 카마이클 수와 소수 판별

λ(n) | (n-1) 이라고 해서 n 이 소수인 것은 아닙니다. 카마이클 수가 이 조건을 만족하는 합성수입니다. 소수 판별에는 밀러-라빈 을 사용하세요.

4. φ(n) 과 혼동

λ(n) <= φ(n) 이지만 같지 않을 수 있습니다. RSA 구현 시 어느 것을 쓰는지 명확히 해야 합니다.

BOJ 연습 문제

번호제목설명
BOJ 17646소수의 개수와 소인수분해소인수분해 기반 계산
BOJ 15711환상의 짝꿍수론 응용

관련 위키

이 글의 용어 (8개)
모듈러 산술 (Modular Arithmetic)algorithm
정의 모듈러 산술 (Modular Arithmetic) 은 정수를 특정 수 $m$ 으로 나눈 나머지 로 대응시켜 다루는 산술 체계입니다. 합동 관계 $a \equiv b \pmo…
소인수분해 (Prime Factorization)algorithm
정의 소인수분해 (Prime Factorization) 는 양의 정수 N을 소수들의 곱으로 유일하게 나타내는 것. N = p₁^a₁ · p₂^a₂ · ... · pₖ^aₖ. 시행…
에라토스테네스의 체 (Sieve of Eratosthenes)algorithm
정의 에라토스테네스의 체 (Sieve of Eratosthenes) 는 1부터 N 까지의 모든 소수를 O(N log log N) 에 찾는 고대 그리스 알고리즘. 기원전 240 년…
오일러 피 함수 (Euler's Totient Function)algorithm
정의 오일러 피 함수 φ(n) 는 1 이상 n 이하의 자연수 중 n 과 서로소인 수의 개수. 1763년 레온하르트 오일러가 도입. , 소수 p 에 대해 . 문제 상황과 동기 n …
정수론 (Number Theory)algorithm
정의 정수론 (Number Theory) 은 정수의 성질과 관계를 연구하는 분야. PS 에서는 약수/배수, 소수 (prime), 모듈러 연산, 유클리드 호제법, 확장 유클리드, …
페르마 소정리 (Fermat's Little Theorem)algorithm
정의 페르마 소정리 (Fermat's Little Theorem, FLT) 는 소수 p 와 gcd(a, p) = 1 인 정수 a 에 대해 다음이 성립: 1640년 피에르 드 페르…
Euler Phi Function: φ(n)algorithm
정의 Euler Phi Function φ(n) 은 1 이상 n 이하 정수 중 n 과 서로소 (gcd = 1) 인 정수의 개수입니다. 예시: - φ(1) = 1 (1은 모든 수와…
Miller-Rabin 소수 판정 (Miller-Rabin Primality Test)algorithm
정의 Miller-Rabin 소수 판정 은 N 이 소수인지 확률적/결정론적으로 판별하는 알고리즘. Fermat 작은 정리와 이차잉여 성질을 결합해 O(k log^3 N) 에 동작…

💬 댓글

사이트 검색 / 명령어

검색

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