https://www.acmicpc.net/problem/2042
N개의 숫자가 주어지고, M번의 숫자 변경과 K번의 구간 합 구하기 연산을 처리해야 하는 문제다. 수의 개수 N은 최대 1,000,000개, 수의 변경 횟수 M은 최대 10,000번, 구간 합을 구하는 횟수 K는 최대 10,000번이다. 입력되는 수와 연산의 개수가 많으므로, 각 연산을 매우 효율적으로 처리해야 시간 초과를 피할 수 있다.
문제의 핵심은 값의 변경과 구간 합 계산이라는 두 가지 연산을 빠르게 수행하는 것이다.
두 연산 모두 빠른 시간 안에 처리하기 위해서는 O(log N) 수준의 시간 복잡도를 갖는 자료구조가 필요하다. 대표적으로 세그먼트 트리(Segment Tree)와 펜윅 트리(Fenwick Tree, 또는 Binary Indexed Tree)가 있다. 이 풀이에서는 펜윅 트리를 사용하여 문제를 해결했다. 펜윅 트리는 세그먼트 트리에 비해 구현이 간단하고, 메모리를 더 적게 사용한다는 장점이 있다.
update: 특정 인덱스의 값을 변경하고, 이와 관련된 구간 합 정보들을 갱신한다.sum: 1번 인덱스부터 특정 인덱스까지의 누적 합을 계산한다.[b, c] 구간의 합은 펜윅 트리의 sum 연산을 이용하여 sum(c) - sum(b-1)으로 구할 수 있다.a = 1 (변경 연산): b번째 수를 c로 변경한다. 원래 arr[b] 값과 c의 차이(diff)를 계산하여 update(b, diff)를 호출한다.a = 2 (구간 합 연산): b부터 c까지의 합을 구한다. query(b, c) 즉, sum(c) - sum(b-1)의 결과를 출력한다.펜윅 트리는 정수의 이진 표현을 활용하여 누적 합을 빠르게 계산하는 자료구조다. tree[i]는 특정 구간의 합을 저장하는데, 그 구간은 i의 이진 표현에서 마지막 1의 위치(Least Significant Bit, LSB)에 의해 결정된다. i & -i 연산은 LSB를 구하는 비트 마스킹 기법이다.
tree[i]가 저장하는 구간: (i - (i & -i) + 1) 부터 i 까지의 합update(idx, diff)idx번째 수가 diff만큼 변했을 때, 이 변경 사항을 트리에 전파하는 함수다.idx부터 시작하여 i += (i & -i) 연산을 통해 부모 노드에 해당하는 인덱스로 이동하며 diff 값을 더해준다.idx를 포함하는 모든 구간 합 정보가 O(log N)의 시간 복잡도로 갱신된다.sum(idx)idx번 인덱스까지의 누적 합을 계산하는 함수다.idx부터 시작하여 i -= (i & -i) 연산을 통해 현재 구간의 합을 더하고, 다음 하위 구간으로 이동한다.sum(7)을 구하려면 tree[7] + tree[6] + tree[4]를 계산하게 된다.i=7 (0111): result += tree[7] -> i = 7 - (7&-7) = 6 (0110)i=6 (0110): result += tree[6] -> i = 6 - (6&-6) = 4 (0100)i=4 (0100): result += tree[4] -> i = 4 - (4&-4) = 0query(start, end)start부터 end까지의 구간 합을 구한다.sum(end) (1부터 end까지의 합)에서 sum(start - 1) (1부터 start-1까지의 합)을 빼서 간단히 구현할 수 있다.package BOJ2042;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
class FenwickTree {
long[] tree;
public FenwickTree(int size) {
tree = new long[size + 1];
}
public void update(int idx, long diff) {
for (int i = idx; i < tree.length; i += (i & -i)) {
tree[i] += diff;
}
}
public long sum(int idx) {
long result = 0;
for (int i = idx; i > 0; i -= (i & -i)) {
result += tree[i];
}
return result;
}
public long query(int start, int end) {
return sum(end) - sum(start - 1);
}
}
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader([System.in](http://System.in)));
StringTokenizer st = new StringTokenizer(br.readLine());
int N = Integer.parseInt(st.nextToken());
int M = Integer.parseInt(st.nextToken());
int K = Integer.parseInt(st.nextToken());
long[] arr = new long[N + 1];
FenwickTree indexTree = new FenwickTree(N);
for (int i = 1; i <= N; i++) {
arr[i] = Long.parseLong(br.readLine());
indexTree.update(i, arr[i]);
}
for (int i = 1; i <= M + K; i++) {
st = new StringTokenizer(br.readLine());
int a = Integer.parseInt(st.nextToken());
int b = Integer.parseInt(st.nextToken());
// c는 update일때는 long, query일때는 int로 사용됨
long c = Long.parseLong(st.nextToken());
if (a == 1) {
//데이터 변경
indexTree.update(b, c - arr[b]);
arr[b] = c;
} else if (a == 2) {
System.out.println(indexTree.query(b, (int)c));
}
}
}
}