소수 판정: 결정론적 AKS 는 O(log^6 N). Miller-Rabin 은 O(k log^3 N) with k 반복.
Shuffling: 정렬 기반 셔플은 중복 편향. Fisher-Yates 는 균일한 순열 보장.
Load Balancing: 해시 테이블, Consistent Hashing.
핵심 통찰: 난수로 입력의 최악의 경우를 회피하거나, 높은 확률로 정답을 보장.
시각화
핵심 아이디어
Las Vegas 알고리즘
1. 항상 올바른 결과 반환2. 런타임이 확률 변수3. 기대 시간 분석 (Expected Time)예: Randomized Quicksort pivot 을 무작위로 선택 최악 입력에도 expected O(N log N)
Monte Carlo 알고리즘
1. 고정된 시간에 실행2. 높은 확률로 정답 (1 - ε)3. 반복 실행으로 오류 확률 지수 감소예: Miller-Rabin k 번 반복하면 합성수를 소수로 오판할 확률 4^(-k)
Seeding
난수 생성기의 초기값을 고정하면:- 디버깅 가능 (재현성)- 대회 환경에서 결과 일관성- mt19937 rng(seed) 또는 uniform_int_distribution
알고리즘
Fisher-Yates Shuffle (Knuth Shuffle): for i = N-1 down to 1: j = random(0, i) // 0 <= j <= i swap(a[i], a[j]) # 모든 N! 순열이 동일 확률Monte Carlo Pi Estimation: inside = 0 for i = 1 to N: x, y = uniform(0, 1) if x² + y² <= 1: inside++ pi ≈ 4 * inside / NRandomized Quickselect: pivot = random element partition around pivot recurse on appropriate side expected O(N)
구현
// Fisher-Yates Shuffle + Monte Carlo Pi estimation// Fixed seed for reproducibility#include <bits/stdc++.h>using namespace std;// Simple LCG for cross-platform determinismint rng_state;int lcg_rand(int n) { rng_state = (rng_state * 1103515245LL + 12345) & 0x7fffffff; return rng_state % n;}int main() { int mode; cin >> mode; rng_state = 42; if (mode == 0) { // Fisher-Yates shuffle int n; cin >> n; vector<int> a(n); for (int i = 0; i < n; i++) a[i] = i + 1; for (int i = n - 1; i > 0; i--) { int j = lcg_rand(i + 1); swap(a[i], a[j]); } for (int x : a) cout << x << " "; cout << "\n"; } else { // Monte Carlo Pi int trials; cin >> trials; int inside = 0; for (int i = 0; i < trials; i++) { int x = lcg_rand(1000001); int y = lcg_rand(1000001); if ((long long)x * x + (long long)y * y <= (long long)1000000 * 1000000) inside++; } double pi = 4.0 * inside / trials; cout << fixed << setprecision(6); cout << pi << "\n"; }}
import randomdef fisher_yates(arr): """In-place Fisher-Yates shuffle, O(N)""" for i in range(len(arr) - 1, 0, -1): j = random.randint(0, i) arr[i], arr[j] = arr[j], arr[i]def monte_carlo_pi(trials: int) -> float: inside = 0 for _ in range(trials): x = random.random() y = random.random() if x * x + y * y <= 1.0: inside += 1 return 4.0 * inside / trialsdef miller_rabin(n: int, k: int = 10) -> bool: """Monte Carlo primality test, error 4^(-k)""" if n < 2: return False if n in (2, 3): return True if n % 2 == 0: return False s, d = 0, n - 1 while d % 2 == 0: s += 1; d //= 2 for _ in range(k): a = random.randrange(2, n - 1) x = pow(a, d, n) if x == 1 or x == n - 1: continue for _ in range(s - 1): x = pow(x, 2, n) if x == n - 1: break else: return False return Truemode = int(input())if mode == 0: n = int(input()) a = list(range(1, n + 1)) random.seed(42) fisher_yates(a) print(" ".join(map(str, a)))else: trials = int(input()) random.seed(42) print(f"{monte_carlo_pi(trials):.6f}")
import java.util.*;import java.io.*;public class Main { static int rngState = 42; static int lcgRand(int n) { rngState = (rngState * 1103515245 + 12345) & 0x7fffffff; return rngState % n; } public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); int mode = Integer.parseInt(br.readLine()); if (mode == 0) { int n = Integer.parseInt(br.readLine()); int[] a = new int[n]; for (int i = 0; i < n; i++) a[i] = i + 1; for (int i = n - 1; i > 0; i--) { int j = lcgRand(i + 1); int t = a[i]; a[i] = a[j]; a[j] = t; } StringBuilder sb = new StringBuilder(); for (int x : a) sb.append(x).append(" "); System.out.println(sb); } else { int trials = Integer.parseInt(br.readLine()); int inside = 0; for (int i = 0; i < trials; i++) { int x = lcgRand(1000001); int y = lcgRand(1000001); if ((long)x * x + (long)y * y <= (long)1000000 * 1000000) inside++; } double pi = 4.0 * inside / trials; System.out.printf("%.6f%n", pi); } }}
stdin
06
결과
3 5 1 6 2 4
stdin
11000000
결과
3.141224
복잡도
항목
값
Fisher-Yates Shuffle
O(N) 시간, O(1) 공간 (in-place)
Monte Carlo Pi
O(N) 시간 (N trials), O(1) 공간
Miller-Rabin (k 반복)
O(k log^3 N) 시간, O(1) 공간
Randomized Quickselect
O(N) expected, O(N^2) worst
Las Vegas vs Monte Carlo 상세
속성
Las Vegas
Monte Carlo
정답 보장
항상
높은 확률
시간
확률적
결정론적
오류 방향
없음
일방향 / 양방향
반복 효과
시간 분산
오류율 지수 감소
예
Randomized Quicksort, Fisher-Yates, Treap
Miller-Rabin, Freivalds, MinHash
Las Vegas -> Monte Carlo 변환
Las Vegas 알고리즘을 시간 제한으로 중단하면 Monte Carlo 로 변환. 시간 제한 내 완료 못하면 “모름” 반환.
오류 확률 감소
Monte Carlo 알고리즘을 k 번 독립적으로 실행하고 다수결로 결과를 취하면 오류 확률이 지수적으로 감소. 1회 오류율이 p < 1/2 이면, k 회 후 오류율 ≤ exp(-2k(1/2 - p)^2) (Chernoff bound).
변형 / 활용
기법
설명
Fisher-Yates Shuffle
균일한 무작위 순열 생성, O(N)
Reservoir Sampling
스트림에서 균일 샘플링, O(1) 공간
Randomized Treap
BST + heap, 균형을 무작위성으로 보장
Bloom Filter
Monte Carlo 집합 멤버십, 거짓 양성 가능
Locality-Sensitive Hashing
근사 유사도 검색
MinHash
집합 유사도 추정 (Jaccard)
Simulated Annealing
무작위성으로 지역 최적 탈출
함정
1. seed 고정과 보안
암호학적 용도로 rand() 나 mt19937 사용 금지. arc4random 또는 CSPRNG 사용. PS 에서는 mt19937 + fixed seed 로 충분.
2. Fisher-Yates 오구현
잘못된 예: for i in 0..N-1: j = random(0, N-1) # N 전체! swap(a[i], a[j])
이 경우 편향된 순열이 생성됨. 올바른 범위는 random(0, i).
3. Monte Carlo 신뢰도
Miller-Rabin 에서 k=1 이면 합성수를 소수로 판정할 확률 1/4. k=10 이면 4^(-10) ≈ 10^(-6). 현장에서는 k=20 이상 권장.
4. 재현성
디버깅과 대회 환경을 위해 seed 고정 필수. 디버그 모드와 배포 모드에서 seed 전략 다르게.
5. modulo 편향
rand() % n 은 n 이 RAND_MAX 의 약수가 아닐 때 편향. C++ 의 uniform_int_distribution 사용.
💬 댓글