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

Fenwick Tree (Binary Indexed Tree): 구간 합 O(log N)

· 수정 · 📖 약 2분 · 707자/단어 #algorithm #data-structure #tree #prefix-sum
Fenwick 배열, Fenwick, Fenwick Tree, 펜윅 트리, BIT, Binary Indexed Tree, 이진 인덱스 트리, 펜윅

정의

Fenwick Tree (또는 BIT, Binary Indexed Tree) 는 배열의 prefix sum 을 O(log N) 에 갱신·조회 하는 자료구조입니다. Peter Fenwick 이 1994 년 발표.

핵심 아이디어: 각 인덱스가 자신의 lowbit 만큼의 구간을 담당 하도록 정의하여, prefix 합을 몇 개의 노드 값의 합으로 표현합니다.

Segment Tree 대신 쓰는가

항목Fenwick TreeSegment Tree
코드 길이매우 짧음 (10줄)김 (30~50줄)
상수매우 작음
지원 연산합, XOR 등 역연산 가능한 것임의 결합 연산 (min, max, gcd)
구간 갱신별도 트릭 필요Lazy propagation 자연스럽게 지원

합 쿼리 위주라면 Fenwick 이 압도적입니다.

lowbit 연산

핵심은 x & -x (2의 보수 특성).

  • x = 6 = 0110x & -x = 0010 = 2
  • x = 12 = 1100x & -x = 0100 = 4
  • x = 8 = 1000x & -x = 1000 = 8

의미: x 의 이진표현에서 가장 낮은 1 비트 만 남긴 값. 이것이 Fenwick 배열에서 그 인덱스가 담당하는 구간의 길이입니다.

구조

인덱스 i (1-based) 는 구간 의 합을 담당합니다.

i:  1 2 3 4 5 6 7 8
    │ ┴ │ ┴ ┴ ┴ │ ┴─────
    │   │       │       ┴
    ┴───┴───────┴───────
1 담당: [1,1]
2 담당: [1,2]
3 담당: [3,3]
4 담당: [1,4]
5 담당: [5,5]
6 담당: [5,6]
7 담당: [7,7]
8 담당: [1,8]

코드 (0-based 배열, 1-based Fenwick)

struct Fenwick {
    vector<long long> t;
    int n;

    Fenwick(int n) : n(n), t(n + 1, 0) {}

    void update(int i, long long v) {  // 1-based
        for (; i <= n; i += i & -i) t[i] += v;
    }

    long long query(int i) {  // 1-based, prefix [1..i]
        long long s = 0;
        for (; i > 0; i -= i & -i) s += t[i];
        return s;
    }

    long long range(int l, int r) {  // [l..r]
        return query(r) - query(l - 1);
    }
};

시간 복잡도: 두 연산 모두 O(log N).

Query 흐름 예 (N=8, query(6))

6 = 110

  1. i=6 (110), t[6] 더함. i -= lowbit(6)=2 → i=4
  2. i=4 (100), t[4] 더함. i -= lowbit(4)=4 → i=0
  3. 종료.

prefix[6] = t[6] + t[4].

Update 흐름 예 (N=8, update(3, +5))

3 = 011

  1. i=3 (011), t[3]+=5. i += lowbit(3)=1 → i=4
  2. i=4 (100), t[4]+=5. i += lowbit(4)=4 → i=8
  3. i=8 (1000), t[8]+=5. i += lowbit(8)=8 → i=16 > 8, 종료.

응용

1. 역수 세기 (Inversion Count)

배열 에서 지만 인 쌍의 개수.

// a 를 오른쪽에서 왼쪽으로 순회
long long inv = 0;
Fenwick f(MAX_VALUE);
for (int i = n - 1; i >= 0; i--) {
    inv += f.query(a[i] - 1);   // 나보다 작은 값이 오른쪽에 몇 개?
    f.update(a[i], 1);
}

2. K-th 원소 (Fenwick 위 이분탐색)

값들에 카운트를 저장하고, prefix 합을 이분탐색으로 뒤져 k 번째 값 찾기. O(log² N).

3. 구간 갱신 + 점 쿼리 (차분 배열 트릭)

update(l, r, v):  fenwick.update(l, +v); fenwick.update(r+1, -v);
query(i):         fenwick.query(i)  // = a[i] 값

4. 구간 갱신 + 구간 쿼리 (Fenwick 2 개)

BIT 두 개 로 임의 구간 갱신, 구간 합을 O(log N) 유지 가능.

2D Fenwick

2 차원 부분합에도 확장:

void update(int x, int y, long long v) {
    for (int i = x; i <= n; i += i & -i)
        for (int j = y; j <= m; j += j & -j)
            t[i][j] += v;
}

long long query(int x, int y) {
    long long s = 0;
    for (int i = x; i > 0; i -= i & -i)
        for (int j = y; j > 0; j -= j & -j)
            s += t[i][j];
    return s;
}

O(log² N), 이미지/그리드 계열 문제에 유용.

함정

  • 인덱스 시작: Fenwick 은 반드시 1-based. i > 0 종료 조건을 잊고 0-based 로 쓰면 무한 루프.
  • 역연산 필요: range(l, r) = query(r) - query(l-1) 이 되려면 역연산 (뺄셈, XOR) 이 있어야 함. min/max 는 Fenwick 로 쿼리 불가 → Segment Tree 사용.
  • 크기 제한: 좌표 압축 잊지 말 것. 값 범위가 크면 index 로 못 씀.

참고

이 글의 용어 (4개)
누적 합 (Prefix Sum)algorithm
정의 누적 합 (Prefix Sum) 은 배열 에 대해 (또는 1-indexed ) 을 미리 계산해 두고, 임의 구간 합 을 O(1) 에 구하는 정형. 문제 풀이에서 "구간 N …
머지 소트 트리 (Merge Sort Tree)algorithm
정의 머지 소트 트리 (Merge Sort Tree) 는 세그먼트 트리의 각 노드에 해당 구간의 정렬된 부분 배열을 저장하는 자료구조. 구간 내에서 k 이하 원소의 개수, k번째…
세그먼트 트리 (Segment Tree)algorithm
정의 세그먼트 트리 (Segment Tree) 는 배열의 구간 쿼리 (range query) 와 점 갱신 (point update) 를 모두 O(log N) 에 처리하는 이진 트…
Wavelet Tree: 값 범위 이진 분할algorithm
정의 Wavelet Tree 는 시퀀스의 값 범위를 이진 분할하며 만든 트리 자료구조입니다. k-th smallest in range, rank/select, count in r…

💬 댓글

사이트 검색 / 명령어

검색

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