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

값 / 좌표 압축 (Coordinate Compression)

· 수정 · 📖 약 3분 · 916자/단어 #algorithm #query #coordinate-compression
coordinate compression, 좌표 압축, 값 압축, 값 매핑, 좌표 압축, compression, discretization

정의

값 / 좌표 압축 (Coordinate Compression) 은 큰 범위의 값 (예: 10^9) 을 정렬된 순서를 유지하며 작은 정수 인덱스 (0..K-1) 로 매핑하는 기법. K = unique value count.

이 기법의 핵심 속성:

  • 순서 보존: 원래 값의 대소 관계가 압축 후에도 동일하게 유지.
  • 가역성: uniq[compressed[i]] == original[i] 로 언제든 복원 가능.
  • 크기 축소: 범위 10^9 → K ≤ N 으로, 희소한 값들을 연속 인덱스로 변환.

문제 상황과 동기

값의 범위가 10^9 이고 입력 크기가 10^5 라면, Fenwick tree 나 segment tree 를 값 자체를 인덱스로 사용할 수 없음. 압축하면 희소한 값들을 연속된 인덱스로 바꾸어 배열 기반 자료구조를 사용 가능.

핵심 통찰: 상대 순서만 중요하지 절대값은 중요하지 않다. 값을 정렬하고 중복 제거한 뒤, 각 값의 순위(index)가 곧 압축된 좌표.

언제 좌표 압축이 필요한가?

flowchart TD
    Start["입력 값 범위가 크다 (10^8 이상)"]
    Start --> Q1{"배열/트리에 값을<br/>직접 인덱스로 쓸 것?"}
    Q1 -->|No| End1["압축 불필요"]
    Q1 -->|Yes| Q2{"입력 크기 N"}
    Q2 -->|"N <= 10^7"| End2["직접 배열 가능"]
    Q2 -->|"N 이 작고 값이 크다"| CC["좌표 압축 적용"]
    CC --> DS["Fenwick, Segment Tree, BIT, 카운팅 배열 등 사용 가능"]

시각화

핵심 아이디어

1. unique = sort(unique(set(original)))   // 정렬 + 중복 제거
2. for each value v in original:
     compressed[v] = lower_bound(unique, v)
     // unique[compressed[v]] == v

압축 후에는 unique[compressed[i]] 로 원래 값을 복원 가능.

알고리즘

compress(a[]):
    sorted = sort unique values of a
    for each x in a:
        idx = lower_bound(sorted, x)
        print idx    // 0-based compressed coordinate

// 예: a = [-5, 10^9, 0, -5, 42]
// sorted = [-5, 0, 42, 10^9]
// compressed = [0, 3, 1, 0, 2]

단계별 흐름

flowchart LR
    A["원본 배열\n[-5, 10^9, 0, -5, 42]"]
    B["유니크 정렬\n[-5, 0, 42, 10^9]"]
    C["이진 탐색 매핑\n[0, 3, 1, 0, 2]"]
    A -->|"sort + unique"| B
    B -->|"lower_bound"| C
    C --> R["Fenwick/SegTree 에 사용"]

구현

// Coordinate compression: O(N log N)
#include <bits/stdc++.h>
using namespace std;

int main() {
  ios::sync_with_stdio(false);
  cin.tie(nullptr);

  int n; cin >> n;
  vector<int> a(n);
  for (auto& v : a) cin >> v;

  // 1. Extract unique sorted values
  vector<int> uniq = a;
  sort(uniq.begin(), uniq.end());
  uniq.erase(unique(uniq.begin(), uniq.end()), uniq.end());

  // 2. Map each original value to compressed index
  for (auto& v : a) {
      v = (int)(lower_bound(uniq.begin(), uniq.end(), v)
                - uniq.begin());
      cout << v << " ";
  }
  cout << "\n";

  // Reconstruct original: uniq[compressed[i]]
  // for (auto v : a) cout << uniq[v] << " ";
}
stdin
7
-5 1000000000 0 -5 42 999999999 0
결과
0 4 1 0 2 3 1

복잡도

항목
시간O(N log N)
공간 (unique 배열)O(K), K = unique value count
압축 크기K ≤ N
복원 (index → value)O(1), uniq[idx]

변형 / 활용

적용처설명
Fenwick / Segment Tree좌표 압축 후 값 범위가 10^9 → 10^5 로 축소
Mo’s algorithm값 범위를 압축해 카운팅 배열 사용 가능
Sweepingx, y 좌표를 각각 압축해 격자로 변환
LIS음수/큰 값 좌표를 순위로 변환해 O(N log N) LIS
2D 압축x, y 각각 압축해 희소 2D 그리드 구성

LIS 와 조합 예시

LIS (Longest Increasing Subsequence) 에서 좌표 압축을 자주 활용한다.

// 값 범위가 크거나 음수를 포함하는 경우
a = [100, -5, 200, -5, 50]

// 압축 후
compressed = [2, 0, 3, 0, 1]

// 이제 Fenwick tree 크기 = K = 4 로 LIS 계산 가능

2D 압축

2D 좌표 문제에서 x, y 를 각각 독립적으로 압축한다. 결과로 (최대 N)×(최대 N) 크기의 2D 배열/트리를 사용 가능.

points = [(10^9, 10^8), (5, 3), (10^9, 3)]
xs = compress([10^9, 5, 10^9])  -> [1, 0, 1]
ys = compress([10^8, 3, 3])     -> [1, 0, 0]
// 결과: (1,1), (0,0), (1,0) -> 3x2 그리드

함정

1. 중복 제거 누락

unique() 또는 set() 으로 중복을 제거하지 않으면, 같은 값이 여러 인덱스를 가져 Fenwick 트리에서 overflow / 잘못된 결과.

// 잘못된 예: sort만 하고 unique 안 함
vector<int> uniq = a;
sort(uniq.begin(), uniq.end());
// uniq = [-5, -5, 0, 42, 10^9] -- 중복 있음!
// -5 의 lower_bound = 0, -5 의 second occurrence = index 1
// 같은 값 두 개가 다른 인덱스를 가짐 -> 오류

2. lower_bound 대신 map 사용

Python 의 dict 나 C++ 의 unordered_map 을 쓰면 O(1) mapping 이 가능하지만, 순서가 필요한 경우 (구간 쿼리, Fenwick) 는 정렬 배열 + binary search 가 필수.

# 경우에 따라 dict 도 OK (순서 필요 없을 때)
rank = {v: i for i, v in enumerate(sorted(set(a)))}
compressed = [rank[v] for v in a]
# 단: range query 에서는 인덱스 순서가 의미를 가져야 하므로 이 방식이 더 명확

3. 0-based vs 1-based

Fenwick tree 는 1-based index 가 필요. 압축 결과가 0-based (lower_bound 기본) 이면 compressed[i] + 1 사용.

// Fenwick tree 와 함께 사용 시
int idx = lower_bound(uniq.begin(), uniq.end(), v) - uniq.begin();
fenwick.update(idx + 1, 1);  // 1-based!

4. 쿼리 값도 함께 압축해야 할 때

범위 쿼리 [L, R] 에서 L, R 도 압축된 인덱스 공간으로 변환해야 한다. 원본 배열과 쿼리 값을 합쳐서 압축하거나, L 에는 lower_bound, R 에는 upper_bound 를 적절히 사용.

BOJ 연습 문제

번호제목정답률링크
BOJ 18870좌표 압축-kokoa-lab
BOJ 1015수열 정렬-kokoa-lab
BOJ 12015가장 긴 증가하는 부분 수열 2-kokoa-lab
BOJ 10815숫자 카드-kokoa-lab

참고

이 글의 용어 (4개)
스위핑 (Sweeping)algorithm
정의 스위핑 (Sweeping) 은 시간 또는 공간 축 위의 이벤트를 정렬한 뒤, 한 방향으로 훑으며 상태를 갱신해 문제를 푸는 기법입니다. 선분 교차, 구간 합집합, 최대 겹침…
이분 탐색 (Binary Search)algorithm
정의 이분 탐색 (Binary Search) 은 정렬된 시퀀스에서 목표값의 위치를 O(log N) 에 찾는 알고리즘. 매 단계에서 후보 구간을 절반으로 줄인다. 탐색이 본질이 아…
Fenwick Tree (Binary Indexed Tree): 구간 합 O(log N)algorithm
정의 Fenwick Tree (또는 BIT, Binary Indexed Tree) 는 배열의 prefix sum 을 O(log N) 에 갱신·조회 하는 자료구조입니다. Peter…
Mo's Algorithm (Mo's)algorithm
정의 Mo's Algorithm 은 오프라인 구간 쿼리를 (L, R) 정렬로 재배치해 포인터 이동을 amortized O((N+Q)√N) 으로 줄이는 기법. 문제 상황과 동기 크…

이 개념을 다룬 위키 페이지 (1)

💬 댓글

사이트 검색 / 명령어

검색

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