길이가 N
인 수열이 주어진다. 수열의 원소는 1 ≤ Ai ≤ K
를 만족하며,
다음과 같은 쿼리를 M
번 수행해야 한다.
A[l] ~ A[r]
에서 같은 값을 가지는 x, y
의 인덱스 간 최대 거리를 구한다.max{|x − y| : l ≤ x, y ≤ r and Ax = Ay}
를 출력한다.각 숫자에 대해 해당 값이 등장한 인덱스를 TreeSet 에 저장하면,
최댓값을 O(1)
에 얻을 수 있다.
positions[A[i]] = {i1, i2, ..., im}
으로 인덱스를 저장.positions[A[i]].last() - positions[A[i]].first()
로 최대 거리 계산.Mo’s Algorithm은 오프라인 쿼리 처리에 적합하며, O(√N * Q)
에 수행 가능하다.
쿼리 정렬
SQRT
크기로 블록을 나누고, (L, R)
기준으로 정렬하여 추가 연산을 줄인다.R
값을 기준으로 정렬한다.투 포인터를 이용해 구간을 조정
L
과 R
을 유지하며 새로운 구간을 추가/제거하여 빠르게 결과를 갱신한다.import java.io.*;
import java.util.*;
public class Main {
static final int MAX_N = 100000;
static final int SQRT = 316;
static int n, k, q;
static int[] arr = new int[MAX_N + 1];
static Query[] queries;
static TreeSet<Integer>[] positions;
static int[] dcnt = new int[MAX_N + 1], sqcnt = new int[(MAX_N / SQRT) + 10], ans;
static class Query implements Comparable<Query> {
int l, r, index;
Query(int l, int r, int index) {
this.l = l;
this.r = r;
this.index = index;
}
@Override
public int compareTo(Query other) {
if (l / SQRT != other.l / SQRT) return Integer.compare(l, other.l);
return Integer.compare(r, other.r);
}
}
static void remove(int idx) {
int num = arr[idx];
if (positions[num].size() >= 2) {
int diff = positions[num].last() - positions[num].first();
dcnt[diff]--;
sqcnt[diff / SQRT]--;
}
positions[num].remove(idx);
if (positions[num].size() >= 2) {
int diff = positions[num].last() - positions[num].first();
dcnt[diff]++;
sqcnt[diff / SQRT]++;
}
}
static void add(int idx) {
int num = arr[idx];
if (positions[num].size() >= 2) {
int diff = positions[num].last() - positions[num].first();
dcnt[diff]--;
sqcnt[diff / SQRT]--;
}
positions[num].add(idx);
if (positions[num].size() >= 2) {
int diff = positions[num].last() - positions[num].first();
dcnt[diff]++;
sqcnt[diff / SQRT]++;
}
}
static int getMaxDistance() {
for (int i = sqcnt.length - 1; i >= 0; i--) {
if (sqcnt[i] > 0) {
for (int j = SQRT - 1; j >= 0; j--) {
int diff = i * SQRT + j;
if (diff < dcnt.length && dcnt[diff] > 0) return diff;
}
}
}
return 0;
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
n = Integer.parseInt(st.nextToken());
k = Integer.parseInt(st.nextToken());
st = new StringTokenizer(br.readLine());
for (int i = 1; i <= n; i++) {
arr[i] = Integer.parseInt(st.nextToken());
}
q = Integer.parseInt(br.readLine());
queries = new Query[q];
ans = new int[q];
positions = new TreeSet[k + 1];
for (int i = 1; i <= k; i++) positions[i] = new TreeSet<>();
for (int i = 0; i < q; i++) {
st = new StringTokenizer(br.readLine());
int l = Integer.parseInt(st.nextToken());
int r = Integer.parseInt(st.nextToken());
queries[i] = new Query(l, r, i);
}
Arrays.sort(queries);
int l = queries[0].l, r = queries[0].r;
for (int i = l; i <= r; i++) add(i);
ans[queries[0].index] = getMaxDistance();
for (int i = 1; i < q; i++) {
while (queries[i].l < l) add(--l);
while (r < queries[i].r) add(++r);
while (l < queries[i].l) remove(l++);
while (queries[i].r < r) remove(r--);
ans[queries[i].index] = getMaxDistance();
}
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
for (int i = 0; i < q; i++) bw.write(ans[i] + "\n");
bw.flush();
}
}
TreeSet을 이용한 최대 거리 계산
TreeSet
에 저장하여 ( O(1) )에 최대 거리 조회 가능.Mo’s Algorithm 활용
오프라인 쿼리 처리
이 문제를 통해 Mo’s Algorithm을 활용한 최댓값 쿼리 처리를 배울 수 있었다.
특히, TreeSet을 사용한 최대 거리 계산과 오프라인 쿼리 최적화 기법이 핵심이었다.