구간 내 고유 요소 개수 구하기 - 펜윅 트리와 오프라인 처리

이 문제는 주어진 배열의 특정 구간에 존재하는 서로 다른 숫자의 개수를 구하는 것을 목표로 합니다. 이를 해결하기 위해 펜윅 트리(Fenwick Tree)와 오프라인 쿼리 처리 기법을 활용합니다.

펜윅 트리를 사용할 때 핵심은 각 위치에서 해당 요소가 마지막으로 등장한 위치를 추적하고, 새로운 위치에서 등장할 경우 이전 위치의 값을 제거하고 현재 위치를 갱신하는 것입니다.

그러나 단순히 순차적으로 처리하면 중복된 요소가 여러 번 카운트되는 문제가 발생할 수 있습니다. 예를 들어, 동일한 값이 여러 위치에 분포되어 있고, 쿼리 범위가 점점 줄어드는 경우 잘못된 결과가 나올 수 있습니다. 이러한 상황을 방지하기 위해 오프라인 쿼리 방식을 적용합니다.

오프라인 처리란 모든 쿼리를 미리 입력받아 특정 기준(예: 구간의 끝점)에 따라 정렬한 후 순차적으로 처리하고, 최종적으로 원래 순서로 결과를 재정렬하여 출력하는 방식입니다. 이를 통해 중복 처리 문제를 효과적으로 회피할 수 있습니다.

구현 방법

쿼리 정보를 저장하는 구조체를 정의하고, 두 가지 기준으로 정렬할 수 있는 비교 함수를 작성합니다:

struct Query {
    int left, right, result, index;
} queries[MAX];

bool compareByRight(Query a, Query b) {
    return a.right < b.right;
}

bool compareById(Query a, Query b) {
    return a.index < b.index;
}

메인 로직에서는 먼저 모든 쿼리를 입력받고, 구간의 끝점을 기준으로 정렬합니다. 이후 배열을 순회하면서 펜윅 트리에 값을 업데이트하고, 현재 위치가 쿼리의 끝점과 일치할 때 해당 구간의 결과를 계산합니다.

완성 코드

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;

const int MAX = 500005;
int tree[MAX], lastPos[1000005], arr[MAX];
int n, m;

inline int lowbit(int x) {
    return x & (-x);
}

void update(int pos, int delta) {
    for (int i = pos; i <= n; i += lowbit(i))
        tree[i] += delta;
}

int prefixSum(int pos) {
    int sum = 0;
    for (int i = pos; i > 0; i -= lowbit(i))
        sum += tree[i];
    return sum;
}

struct Query {
    int left, right, answer, idx;
} q[MAX];

bool sortByRight(Query a, Query b) {
    return a.right < b.right;
}

bool sortById(Query a, Query b) {
    return a.idx < b.idx;
}

int main() {
    ios::sync_with_stdio(false);
    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> arr[i];

    cin >> m;
    for (int i = 1; i <= m; i++) {
        cin >> q[i].left >> q[i].right;
        q[i].idx = i;
    }

    sort(q + 1, q + m + 1, sortByRight);

    int ptr = 1;
    for (int i = 1; i <= n; i++) {
        if (lastPos[arr[i]]) {
            update(lastPos[arr[i]], -1);
        }
        update(i, 1);
        lastPos[arr[i]] = i;

        while (ptr <= m && q[ptr].right == i) {
            q[ptr].answer = prefixSum(q[ptr].right) - prefixSum(q[ptr].left - 1);
            ptr++;
        }
    }

    sort(q + 1, q + m + 1, sortById);
    for (int i = 1; i <= m; i++)
        cout << q[i].answer << "\n";

    return 0;
}

태그: Fenwick Tree Offline Query algorithm Bit Manipulation Data Structure

7월 17일 22:50에 게시됨