PrefixSum/Interval
1. Prefix Sum (누적합)
기본 개념: 누적합을 사용하면 배열의 특정 구간의 합을 O(1) 시간 복잡도로 구할 수 있습니다.
1-1. 1차원 배열에서 Prefix Sum
int[] computePrefixSum(int[] arr) {
int[] prefixSum = new int[arr.length];
prefixSum[0] = arr[0];
for (int i = 1; i < arr.length; i++) {
prefixSum[i] = prefixSum[i - 1] + arr[i]; // 현재까지의 합 계산
}
return prefixSum;
}
// 구간 [l, r]의 합 구하기
int rangeSum(int[] prefixSum, int l, int r) {
if (l == 0) {
return prefixSum[r]; // 배열의 처음부터 r까지의 합
}
return prefixSum[r] - prefixSum[l - 1]; // 구간 [l, r]의 합
}
1-2. 2차원 배열에서 Prefix Sum (누적합)
int[][] computePrefixSum2D(int[][] arr) {
int n = arr.length;
int m = arr[0].length;
int[][] prefixSum = new int[n][m];
// 첫 번째 행 및 열 처리
prefixSum[0][0] = arr[0][0];
for (int i = 1; i < n; i++) prefixSum[i][0] = prefixSum[i - 1][0] + arr[i][0];
for (int j = 1; j < m; j++) prefixSum[0][j] = prefixSum[0][j - 1] + arr[0][j];
// 나머지 행과 열 처리
for (int i = 1; i < n; i++) {
for (int j = 1; j < m; j++) {
prefixSum[i][j] = arr[i][j] + prefixSum[i - 1][j] + prefixSum[i][j - 1] - prefixSum[i - 1][j - 1];
}
}
return prefixSum;
}
// 2D 구간 [x1, y1]에서 [x2, y2]까지의 합 구하기
int rangeSum2D(int[][] prefixSum, int x1, int y1, int x2, int y2) {
int result = prefixSum[x2][y2];
if (x1 > 0) result -= prefixSum[x1 - 1][y2];
if (y1 > 0) result -= prefixSum[x2][y1 - 1];
if (x1 > 0 && y1 > 0) result += prefixSum[x1 - 1][y1 - 1];
return result;
}
2. Interval Sum (구간합)
Segment Tree를 이용해 구간합을 빠르게 구할 수 있습니다.
2-1. Segment Tree를 사용한 구간합
class SegmentTree {
int[] tree;
int n;
public SegmentTree(int[] arr) {
n = arr.length;
tree = new int[4 * n]; // 세그먼트 트리 크기
build(arr, 0, 0, n - 1);
}
// 세그먼트 트리 빌드
private void build(int[] arr, int node, int start, int end) {
if (start == end) {
tree[node] = arr[start];
} else {
int mid = (start + end) / 2;
build(arr, 2 * node + 1, start, mid);
build(arr, 2 * node + 2, mid + 1, end);
tree[node] = tree[2 * node + 1] + tree[2 * node + 2];
}
}
// 구간합 쿼리
public int rangeSum(int l, int r) {
return query(0, 0, n - 1, l, r);
}
private int query(int node, int start, int end, int l, int r) {
if (r < start || l > end) {
return 0; // 범위를 벗어난 경우
}
if (l <= start && end <= r) {
return tree[node]; // 구간 내에 완전히 포함된 경우
}
int mid = (start + end) / 2;
int leftSum = query(2 * node + 1, start, mid, l, r);
int rightSum = query(2 * node + 2, mid + 1, end, l, r);
return leftSum + rightSum;
}
// 업데이트
public void update(int index, int value) {
update(0, 0, n - 1, index, value);
}
private void update(int node, int start, int end, int index, int value) {
if (start == end) {
tree[node] = value;
} else {
int mid = (start + end) / 2;
if (index <= mid) {
update(2 * node + 1, start, mid, index, value);
} else {
update(2 * node + 2, mid + 1, end, index, value);
}
tree[node] = tree[2 * node + 1] + tree[2 * node + 2];
}
}
}
2-2. Fenwick Tree (Binary Indexed Tree)
Fenwick Tree는 구간 합 및 업데이트를 O(log n) 시간에 수행할 수 있는 자료 구조입니다.
class FenwickTree {
int[] bit;
int n;
public FenwickTree(int n) {
this.n = n;
bit = new int[n + 1]; // 1-based 인덱싱
}
// 업데이트
public void update(int index, int value) {
for (; index <= n; index += index & -index) {
bit[index] += value;
}
}
// 구간합 쿼리
public int prefixSum(int index) {
int sum = 0;
for (; index > 0; index -= index & -index) {
sum += bit[index];
}
return sum;
}
// 구간합 [l, r]
public int rangeSum(int l, int r) {
return prefixSum(r) - prefixSum(l - 1);
}
}
Last updated