1. 2차원 누적합이란?
누적합 배열 P는 원본 배열 A의 (0,0)부터 (i, j)까지의 합을 저장합니다.
즉,
- P[i][j] = A[0][0] + A[0][1] + … + A[i-1][j-1] (패딩 때문에 인덱스가 1씩 밀림)
패딩을 적용하면 *P[0][]와 P[][0]를 모두 0으로 채워서,
계산할 때 경계 조건을 신경쓰지 않아도 됩니다.
예를 들어, 누적합 배열의 크기는 (n+1)×(n+1)이 되어,
실제 원본 배열의 값은 P[1][1]부터 P[n][n]에 저장됩니다.
2. 누적합 배열 생성
2.1 왜 패딩을 사용할까?
패딩을 사용하면
- 첫 행과 첫 열에 대해 별도의 조건문 없이 계산할 수 있습니다.
- 인덱스 접근 시 “범위 초과” 문제를 걱정할 필요가 없으므로 코드가 간결해집니다.
2.2 누적합 배열 공식
패딩을 적용한 누적합 배열 P를 만드는 공식은 다음과 같습니다.
P[i][j] = A[i-1][j-1] + P[i-1][j] + P[i][j-1] - P[i-1][j-1] (for i, j ≥ 1)
- A[i-1][j-1]: 원본 배열의 현재 값
- P[i-1][j]: 위쪽 누적합
- P[i][j-1]: 왼쪽 누적합
- P[i-1][j-1]: 위쪽과 왼쪽이 겹치는 영역 (중복 제거용)
2.3 예시: 5×5 배열 누적합 만들기
원본 배열 A
| 0 | 1 | 2 | 3 | 4 | |
|---|---|---|---|---|---|
| 0 | 3 | 1 | 9 | 2 | 3 |
| 1 | 9 | 2 | 3 | 8 | 1 |
| 2 | 2 | 3 | 1 | 6 | 3 |
| 3 | 5 | 6 | 6 | 8 | 5 |
| 4 | 8 | 5 | 2 | 3 | 7 |
패딩된 누적합 배열 P
| 0 | 1 | 2 | 3 | 4 | 5 | |
|---|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 1 | 0 | 3 | 4 | 13 | 15 | 18 |
| 2 | 0 | 12 | 15 | 27 | 37 | 41 |
| 3 | 0 | 14 | 20 | 33 | 49 | 56 |
| 4 | 0 | 19 | 31 | 50 | 74 | 86 |
| 5 | 0 | 27 | 44 | 65 | 92 | 111 |
계산 예시:
P[1][3] 계산
– A[0][2] = 9
– P[0][3] = 0 (패딩된 첫 행)
– P[1][2] = 4
– P[0][2] = 0
따라서, P[1][3] = 9 + 0 + 4 – 0 = 13
3. 구간 합 계산: 공식의 의미와 사용법
누적합 배열을 사용하면, 원본 배열 A의 임의의 직사각형 영역의 합을 아래 공식으로 O(1)에 계산할 수 있습니다.
구간 합 = P[x2][y2] - P[x1-1][y2] - P[x2][y1-1] + P[x1-1][y1-1]
3.1 각 항의 의미

- P[x2][y2]
- P[x1-1][y2]
- P[x2][y1-1]
- P[x1-1][y1-1]
예시:
(2,2)부터 (4,4) 구간을 구한다고 할 때,
– P[4][4]: (1,1)부터 (4,4)까지 전체 합
– P[1][4]: (1,1)부터 (1,4)까지의 위쪽 영역 (제거)
– P[4][1]: (1,1)부터 (4,1)까지의 왼쪽 영역 (제거)
– P[1][1]: 중복 제거된 영역 → 보충
예를 들어, 직접 계산한 구간 합이 43이라면 위 공식으로도 43이 나옵니다.
4. 코드 구현 예시
문제(
1
2
3
4
2
3
4
5
3
4
5
6
4
5
6
7
여기서 (2, 2)부터 (3, 4)까지 합을 구하면 3+4+5+4+5+6 = 27이고, (4, 4)부터 (4, 4)까지 합을 구하면 7이다.
표에 채워져 있는 수와 합을 구하는 연산이 주어졌을 때, 이를 처리하는 프로그램을 작성하시오.
입력
첫째 줄에 표의 크기 N과 합을 구해야 하는 횟수 M이 주어진다. (1 ≤ N ≤ 1024, 1 ≤ M ≤ 100,000) 둘째 줄부터 N개의 줄에는 표에 채워져 있는 수가 1행부터 차례대로 주어진다. 다음 M개의 줄에는 네 개의 정수 x1, y1, x2, y2 가 주어지며, (x1, y1)부터 (x2, y2)의 합을 구해 출력해야 한다. 표에 채워져 있는 수는 1,000보다 작거나 같은 자연수이다. (x1 ≤ x2, y1 ≤ y2)
출력
총 M줄에 걸쳐 (x1, y1)부터 (x2, y2)까지 합을 구해 출력한다.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class Main {
public static void main(String[] args) throws IOException {
// 입력 처리: 첫 줄에 배열의 크기 n과 구간 합 쿼리 수 m이 주어진다.
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String[] tokens = br.readLine().split(" ");
int n = Integer.parseInt(tokens[0]);
int m = Integer.parseInt(tokens[1]);
// 원본 배열 A (0-index 기준) 입력 받기
int[][] A = new int[n][n];
for (int i = 0; i < n; i++) {
tokens = br.readLine().split(" ");
for (int j = 0; j < n; j++) {
A[i][j] = Integer.parseInt(tokens[j]);
}
}
// 누적합 배열 P 생성 (패딩 적용: 크기 (n+1) x (n+1), P[0][*]와 P[*][0]는 0)
int[][] P = new int[n+1][n+1];
// P 배열은 1-index부터 값이 채워진다. 즉, 원본 배열 A의 A[i][j]는 P[i+1][j+1]에 반영됨.
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
P[i][j] = A[i-1][j-1] + P[i-1][j] + P[i][j-1] - P[i-1][j-1];
}
}
// M개의 구간 합 쿼리 처리 (쿼리 좌표는 1-index로 주어진다)
StringBuilder sb = new StringBuilder();
for (int k = 0; k < m; k++) {
tokens = br.readLine().split(" ");
// 입력 좌표는 1-index이므로 그대로 사용 (패딩 배열 P에 맞춰 계산)
int x1 = Integer.parseInt(tokens[0]);
int y1 = Integer.parseInt(tokens[1]);
int x2 = Integer.parseInt(tokens[2]);
int y2 = Integer.parseInt(tokens[3]);
int result = P[x2][y2] - P[x1-1][y2] - P[x2][y1-1] + P[x1-1][y1-1];
sb.append(result).append("\n");
}
System.out.print(sb);
}
}
코드 상세 설명
- 입력 처리:
첫 줄에서 n과 m을 읽어오고,
이후 n개의 줄에 걸쳐 원본 배열 A를 0-index 방식으로 입력받습니다.
- 누적합 배열 P 생성 (패딩 적용):
배열 P의 크기는 (n+1)×(n+1)이며,
P[0][]와 P[*][0]는 0으로 초기화되어 경계 처리를 간단하게 만듭니다.
for문을 돌며, P[i][j]를 A[i-1][j-1]과 위쪽, 왼쪽, 대각선 위쪽 값을 이용해 계산합니다.
- 구간 합 쿼리 처리:
입력된 쿼리 좌표는 1-index로 주어지므로,
누적합 공식 (P[x2][y2] – P[x1-1][y2] – P[x2][y1-1] + P[x1-1][y1-1])를 그대로 적용하여 결과를 계산합니다.
참고
https://www.youtube.com/watch?v=KT864Aa3zE0
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class Main {
public static void main(String[] args) throws IOException {
// 입력 처리: 첫 줄에 배열의 크기 n과 구간 합 쿼리 수 m이 주어진다.
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String[] tokens = br.readLine().split(" ");
int n = Integer.parseInt(tokens[0]);
int m = Integer.parseInt(tokens[1]);
// 원본 배열 A (0-index 기준) 입력 받기
int[][] A = new int[n][n];
for (int i = 0; i < n; i++) {
tokens = br.readLine().split(" ");
for (int j = 0; j < n; j++) {
A[i][j] = Integer.parseInt(tokens[j]);
}
}
// 누적합 배열 P 생성 (패딩 적용: 크기 (n+1) x (n+1), P[0][*]와 P[*][0]는 0)
int[][] P = new int[n+1][n+1];
// P 배열은 1-index부터 값이 채워진다. 즉, 원본 배열 A의 A[i][j]는 P[i+1][j+1]에 반영됨.
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
P[i][j] = A[i-1][j-1] + P[i-1][j] + P[i][j-1] - P[i-1][j-1];
}
}
// M개의 구간 합 쿼리 처리 (쿼리 좌표는 1-index로 주어진다)
StringBuilder sb = new StringBuilder();
for (int k = 0; k < m; k++) {
tokens = br.readLine().split(" ");
// 입력 좌표는 1-index이므로 그대로 사용 (패딩 배열 P에 맞춰 계산)
int x1 = Integer.parseInt(tokens[0]);
int y1 = Integer.parseInt(tokens[1]);
int x2 = Integer.parseInt(tokens[2]);
int y2 = Integer.parseInt(tokens[3]);
int result = P[x2][y2] - P[x1-1][y2] - P[x2][y1-1] + P[x1-1][y1-1];
sb.append(result).append("\n");
}
System.out.print(sb);
}
}