배열 A
가 주어지고, 다음과 같은 Q
개의 쿼리를 처리하는 문제이다:
l r
l
번째부터 r
번째까지의 구간에서 서로 다른 수의 개수를 출력한다.N
: 최대 1,000,000Q
: 최대 1,000,000[l, r]
에서 서로 다른 수의 개수를 효율적으로 계산해야 한다.import java.io.*;
import java.util.*;
public class Main {
static class Query implements Comparable<Query> {
int idx, left, right, block;
Query(int idx, int left, int right, int block) {
this.idx = idx;
this.left = left;
this.right = right;
this.block = block;
}
@Override
public int compareTo(Query other) {
if (this.block != other.block) {
return Integer.compare(this.block, other.block);
}
return Integer.compare(this.right, other.right);
}
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
int n = Integer.parseInt(br.readLine());
int sqrtN = (int) Math.sqrt(n);
int[] arr = new int[n];
StringTokenizer st = new StringTokenizer(br.readLine());
for (int i = 0; i < n; i++) {
arr[i] = Integer.parseInt(st.nextToken());
}
// Coordinate compression
int[] compressed = Arrays.copyOf(arr, n);
Arrays.sort(compressed);
Map<Integer, Integer> valueToIndex = new HashMap<>();
int idx = 0;
for (int val : compressed) {
if (!valueToIndex.containsKey(val)) {
valueToIndex.put(val, idx++);
}
}
for (int i = 0; i < n; i++) {
arr[i] = valueToIndex.get(arr[i]);
}
int q = Integer.parseInt(br.readLine());
Query[] queries = new Query[q];
int[] results = new int[q];
for (int i = 0; i < q; i++) {
st = new StringTokenizer(br.readLine());
int l = Integer.parseInt(st.nextToken()) - 1;
int r = Integer.parseInt(st.nextToken()) - 1;
queries[i] = new Query(i, l, r, l / sqrtN);
}
Arrays.sort(queries);
int[] freq = new int[n];
int distinctCount = 0;
int currentLeft = 0, currentRight = -1;
for (Query query : queries) {
while (currentRight < query.right) {
currentRight++;
distinctCount += add(arr[currentRight], freq);
}
while (currentRight > query.right) {
distinctCount -= remove(arr[currentRight], freq);
currentRight--;
}
while (currentLeft < query.left) {
distinctCount -= remove(arr[currentLeft], freq);
currentLeft++;
}
while (currentLeft > query.left) {
currentLeft--;
distinctCount += add(arr[currentLeft], freq);
}
results[query.idx] = distinctCount;
}
for (int result : results) {
bw.write(result + "\n");
}
bw.flush();
bw.close();
}
private static int add(int value, int[] freq) {
if (freq[value] == 0) {
freq[value]++;
return 1;
}
freq[value]++;
return 0;
}
private static int remove(int value, int[] freq) {
if (freq[value] == 1) {
freq[value]--;
return 1;
}
freq[value]--;
return 0;
}
}
a[i]
의 범위가 최대 (10^9)이므로, 각 값을 압축된 값으로 치환하여 메모리 사용량 및 처리 효율을 높인다. int[] compressed = Arrays.copyOf(arr, n);
Arrays.sort(compressed);
Map<Integer, Integer> valueToIndex = new HashMap<>();
int idx = 0;
for (int val : compressed) {
if (!valueToIndex.containsKey(val)) {
valueToIndex.put(val, idx++);
}
}
for (int i = 0; i < n; i++) {
arr[i] = valueToIndex.get(arr[i]);
}
(l)
을 기준으로 정렬.(r)
을 기준으로 정렬.int sqrtN = (int) Math.sqrt(n);
Query[] queries = new Query[q];
for (int i = 0; i < q; i++) {
int l = Integer.parseInt(st.nextToken()) - 1;
int r = Integer.parseInt(st.nextToken()) - 1;
queries[i] = new Query(i, l, r, l / sqrtN);
}
Arrays.sort(queries);
[currentLeft, currentRight]
를 유지하며, 각 쿼리에 맞게 구간을 이동한다.for (Query query : queries) {
while (currentRight < query.right) {
currentRight++;
distinctCount += add(arr[currentRight], freq);
}
while (currentRight > query.right) {
distinctCount -= remove(arr[currentRight], freq);
currentRight--;
}
while (currentLeft < query.left) {
distinctCount -= remove(arr[currentLeft], freq);
currentLeft++;
}
while (currentLeft > query.left) {
currentLeft--;
distinctCount += add(arr[currentLeft], freq);
}
results[query.idx] = distinctCount;
}
add
함수:
distinctCount
를 증가시킨다. remove
함수:
distinctCount
를 감소시킨다.private static int add(int value, int[] freq) {
if (freq[value] == 0) {
freq[value]++;
return 1;
}
freq[value]++;
return 0;
}
private static int remove(int value, int[] freq) {
if (freq[value] == 1) {
freq[value]--;
return 1;
}
freq[value]--;
return 0;
}
이 문제는 Mo's Algorithm을 활용하여 효율적으로 쿼리를 처리한 예시이다.
가장 큰 고민은 ( O(N + Q\sqrt{N}) )로 처리 가능한 알고리즘을 설계했으며, 좌표 압축과 구간 이동을 결합하여 메모리 및 처리 시간을 최적화했다.
실제 구현에서는 ( N )과 ( Q )가 매우 클 수 있기 때문에, 메모리 관리와 효율성에 초점을 맞췄다.