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

희소 배열 (Sparse Table)

· 수정 · 📖 약 2분 · 726자/단어 #algorithm #data-structure #sparse-table #rmq
Sparse Table, sparse table, 희소 배열, 희소 테이블, sparse-table

정의

희소 배열 (Sparse Table)정적 배열에서 결합 법칙을 만족하는 idempotent 연산 (min, max, gcd, lcm 등) 의 구간 쿼리를 O(1) 시간에 처리하는 자료구조. 전처리 O(N log N), 공간 O(N log N).

idempotent: f(a, a) = a 인 성질 (min, max, gcd 는 만족, sum 은 불만족).

문제 상황과 동기

길이 N 배열에서 RMQ (Range Minimum Query) min(a[l..r]) 을 Q 번 물을 때:

  • naive: 매 쿼리마다 O(r - l + 1) = O(N). Q 개면 O(N · Q).
  • 세그먼트 트리: O(N log N) 전처리, O(log N) 쿼리. 갱신도 O(log N).
  • 희소 배열: 갱신 없이 쿼리만 있으면 O(1) 에 답 가능. 전처리 O(N log N).

핵심 통찰: 모든 구간 [l, r]두 개의 2^k 길이 블록 으로 덮을 수 있음. idempotent 성질 덕분에 중복 허용.

실무: 로그 분석, 센서 데이터 이상치 탐지 등 대량 읽기 / 갱신 없음.

시각화

핵심 아이디어

테이블 st[i][j] = [i, i + 2^j - 1] 구간의 min (또는 max / gcd).

st[i][0] = a[i]                               (길이 1)
st[i][j] = f(st[i][j-1], st[i + 2^(j-1)][j-1])  (두 반쪽 merge)

쿼리 [l, r] 시:

k = floor(log2(r - l + 1))
answer = f(st[l][k], st[r - 2^k + 1][k])

두 블록이 겹쳐도 idempotent 때문에 min / max / gcd 결과는 동일.

알고리즘

preprocess(a, N):
    K = floor(log2(N)) + 1
    for i in 0..N-1:
        st[i][0] = a[i]
    for j in 1..K:
        for i in 0..(N - 2^j):
            st[i][j] = f(st[i][j-1], st[i + 2^(j-1)][j-1])

query(l, r):
    k = floor(log2(r - l + 1))
    return f(st[l][k], st[r - 2^k + 1][k])

구현

// Sparse Table for RMQ, O(N log N) build + O(1) query
#include <bits/stdc++.h>
using namespace std;

int main() {
  int n, q; cin >> n >> q;
  vector<int> a(n);
  for (auto& v : a) cin >> v;

  int K = __lg(n) + 1;
  vector<vector<int>> st(n, vector<int>(K));
  for (int i = 0; i < n; i++) st[i][0] = a[i];
  for (int j = 1; j < K; j++) {
      for (int i = 0; i + (1 << j) <= n; i++)
          st[i][j] = min(st[i][j-1], st[i + (1 << (j-1))][j-1]);
  }

  while (q--) {
      int l, r; cin >> l >> r; l--; r--;  // 0-indexed [l, r]
      int k = __lg(r - l + 1);
      cout << min(st[l][k], st[r - (1 << k) + 1][k]) << "\n";
  }
}
stdin
5 3
3 1 4 1 5
1 3
2 5
1 5
결과
1
1
1

복잡도

항목
전처리 시간O(N log N)
전처리 공간O(N log N)
쿼리 시간O(1)
갱신불가 (정적)
적용 연산idempotent + 결합법칙 (min, max, gcd, lcm)

변형 / 활용

1. max query

minmax 로 바꾸면 RMaxQ.

2. gcd query

f(x, y) = gcd(x, y) 도 idempotent. st[i][j] = gcd(st[i][j-1], st[i + 2^(j-1)][j-1]).

3. LCA (Lowest Common Ancestor)

트리를 Euler tour + depth 배열로 변환하면 LCA 를 RMQ 로. O(N log N) 전처리, O(1) LCA.

4. 2D Sparse Table

2D 배열 st[i][j][ki][kj] 로 확장 가능. 전처리 O(N M log N log M), 쿼리 O(1).

함정

1. sum / xor 은 불가

idempotent 하지 않으면 중복 영역 두 번 계산. sum 은 누적 합 또는 세그먼트 트리.

2. log2 계산

C++ __lg(x) = floor(log2(x)). Python x.bit_length() - 1. Java 31 - Integer.numberOfLeadingZeros(x).

3. 메모리

N=10^5, K=17 이면 N × K ≈ 1.7M 배열. N=10^6 면 17M, 정수 4B 기준 68MB. 큰 N 주의.

4. 1-indexed vs 0-indexed

쿼리 입력 1-indexed 면 l--, r-- 필수.

BOJ 연습 문제

번호제목정답률링크
BOJ 17435합성함수와 쿼리-kokoa-lab
BOJ 3117YouTube-kokoa-lab
BOJ 11440피보나치 수의 제곱의 합-kokoa-lab

참고

이 글의 용어 (3개)
누적 합 (Prefix Sum)algorithm
정의 누적 합 (Prefix Sum) 은 배열 에 대해 (또는 1-indexed ) 을 미리 계산해 두고, 임의 구간 합 을 O(1) 에 구하는 정형. 문제 풀이에서 "구간 N …
세그먼트 트리 (Segment Tree)algorithm
정의 세그먼트 트리 (Segment Tree) 는 배열의 구간 쿼리 (range query) 와 점 갱신 (point update) 를 모두 O(log N) 에 처리하는 이진 트…
최소 공통 조상 (Lowest Common Ancestor)algorithm
정의 최소 공통 조상 (LCA, Lowest Common Ancestor) 는 트리에서 두 노드 u, v 의 공통 조상 중 가장 깊은 (루트에서 가장 먼) 노드. Binary L…

💬 댓글

사이트 검색 / 명령어

검색

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