가장 먼저 떠오르는 방법은 질의가 들어올 때마다 이중 반복문을 통해 범위를 순회하며 더하는 것입니다. 하지만 이 방식의 시간 복잡도를 계산해 봅시다.
따라서, 우리는 질의(Query)가 들어올 때마다 에 답을 낼 수 있는 전처리(Pre-computation) 과정이 필요합니다. 이때 사용하는 알고리즘이 바로 2차원 누적 합(2D Prefix Sum)입니다.
2차원 배열에서 누적 합을 구하거나 특정 구간의 합을 구할 때는 집합의 포함-배제의 원리(Inclusion-Exclusion Principle)와 유사한 기하학적 아이디어를 사용합니다.
dp[i][j]): 부터 까지의 사각형 영역 전체 합을 미리 구해둡니다.Step 1: 누적 합 배열 채우기
현재 위치 까지의 누적 합은 [위쪽 누적 합] + [왼쪽 누적 합] - [대각선 위 중복 영역] + [현재 값]입니다.
Step 2: 정답 도출하기
부터 까지의 부분 합은 다음과 같습니다.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
class Main {
static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
static StringTokenizer st;
public static void main(String[] args) throws IOException {
st = new StringTokenizer(br.readLine());
int N = Integer.parseInt(st.nextToken());
int M = Integer.parseInt(st.nextToken());
int[][] A = new int[N + 1][N + 1];
// 원본 배열 입력
for (int i = 1; i <= N; i++) {
st = new StringTokenizer(br.readLine());
for (int j = 1; j <= N; j++) {
A[i][j] = Integer.parseInt(st.nextToken());
}
}
// DP 테이블 선언
int[][] dp = new int[N + 1][N + 1];
// 1. 첫 행과 첫 열 초기화 (Boundary Condition)
for (int i = 1; i <= N; i++) {
dp[i][1] = dp[i - 1][1] + A[i][1];
dp[1][i] = dp[1][i - 1] + A[1][i];
}
// 2. 나머지 DP 테이블 채우기 (점화식 적용)
for (int i = 2; i <= N; i++) {
for (int j = 2; j <= N; j++) {
dp[i][j] = A[i][j] + dp[i - 1][j] + dp[i][j - 1] - dp[i - 1][j - 1];
}
}
StringBuilder sb = new StringBuilder();
// 3. 쿼리 처리 (M번)
for (int i = 0; i < M; i++) {
st = new StringTokenizer(br.readLine());
int x1 = Integer.parseInt(st.nextToken());
int y1 = Integer.parseInt(st.nextToken());
int x2 = Integer.parseInt(st.nextToken());
int y2 = Integer.parseInt(st.nextToken());
// 구간 합 공식 적용
int answer = dp[x2][y2] - dp[x1 - 1][y2] - dp[x2][y1 - 1] + dp[x1 - 1][y1 - 1];
sb.append(answer).append('\n');
}
System.out.println(sb);
}
}