| 시작하기 전에
지난번에는 일차원 배열에 있는 데이터에 대한 구간합을 구하는 법을 다루었다면,
이번에는 이차원 배열에 있는 데이터에 대한 구간합을 구하는 법을 다루어보았다.
🛎 11660 구간 합 구하기 5
| 문제
N×N개의 수가 N×N 크기의 표에 채워져 있다. (x1, y1)부터 (x2, y2)까지 합을 구하는 프로그램을 작성하시오. (x, y)는 x행 y열을 의미한다.
예를 들어, N = 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)까지 합을 구해 출력한다.
| 예제 입력
4 3
1 2 3 4
2 3 4 5
3 4 5 6
4 5 6 7
2 2 3 4
3 4 3 4
1 1 4 4
| 예제 출력
27
6
64
🔎 문제분석
이 문제도 지난번 일차원 구간합 구하기 문제처럼, 반복문을 사용하면 시간 초과가 난다.
[1] 자료형을 뭘 써야할까? 만약, 여기서 단순 반복을 사용하면 얼마나 오랜 시간이 걸릴까?
아래는 입력값들의 범위인데,
변수명 | 입력값 | 범위 |
N | 표의 크기 (행의 크기) | 1 ~ 1,024 |
데이터 | 배열의 데이터 | 1 ~ 1,000 |
M | 합을 구해야하는 횟수 | 1 ~ 100,000 |
- 표의 크기는 최대 1024 x 1024 이며, 그렇게 되면 최대 1,048,576개의 데이터 수를 받게 된다.
- 데이터의 크기는 최대 1000인데, 최대 표 크기에서 모든 데이터가 1000이라고 할 때,
최대 범위인 (1, 1)부터 (N, N)까지 최대 구간합은 1,048,576,000가 된다.
- int타입 데이터의 표현 범위는 -2,147,483,648 ~ 2,147,483,647(약 -21억 ~ 21억, -2^31 ~ 2^31 - 1)이기 때문에
구간합의 자료형은 int로 두어도 괜찮다.
이제 단순 반복을 했을 때에 얼마나 시간이 걸릴 것인가를 구해 보려고 한다.
[행 x 열]에 대한 이중 반복 루프에서 각 (x1, y1) ~ (x2, y2)까지의 합을 구한다고 하면
- 시간 복잡도는 O(n^2)가 나온다. (만약 M만큼 범위를 입력받으면서 동시에 구간합을 처리하게 되면 O(n^3) )
만약에 최대 크기 배열에 M이 최댓값이 나온다하고 범위가 (1, 1) ~ (N, N)이면- 100,000 x 1024 x 1024(104,857,600,000)만큼 데이터를 확인하고 합을 구한다.
[2] 2차원 배열의 구간합을 구하는 방법을 구해보자..
(1) 먼저, 입력 배열은 D[N + 1][N + 1]로, 구간합 배열은 S[N + 1][N + 1]로 만들려 한다.
- 배열 크기는 입력을 (1, 1)부터 (N, N) 사이값으로 받기 때문에 N+1 x N+1으로 하는게 더 쉬웠다..
- (D는 dataArr의 약자고, S는 sumArr의 약자로 임의로 잡았다.)
(2) D --> S 로 구간합을 구하는 방법이다.
가. 먼저 행이 1일 때와, 열이 1일 때를 보면
1 | 2 | 3 | 4 | 구간합 --------> |
1 | 2 | 3 | 4 | ||
1 | 1 | 2 | 3 | 4 | 1 | 1 | 3 | 6 | 10 | |
2 | 2 | 3 | 4 | 5 | 2 | 3 | ||||
3 | 3 | 4 | 5 | 6 | 3 | 6 | ||||
4 | 4 | 5 | 6 | 7 | 4 | 10 |
- 행이 1인 경우에는, S배열에서 바로 전 열(j - 1)의 값과 D[ i ][ j ] 를 더해주면 된다.
S [ i ][ j ] = S [ i ][ j - 1 ] + D[ i ][ j ]
- 열이 1인 경우에도, S배열에서 바로 전 행(i - 1)의 값과 D[ i ][ j ] 를 더해준다.
S [ i ][ j ] = S [ i - 1 ][ j ] + D[ i ][ j ]
나. 행과 열이 1이 아닌 경우를 보면
1 | 2 | 3 | 4 | 구간합 --------> |
1 | 2 | 3 | 4 | ||
1 | 1 | 2 | 3 | 4 | 1 | 1 | 3 | 6 | 10 | |
2 | 2 | 3 | 4 | 5 | 2 | 3 | 8 | |||
3 | 3 | 4 | 5 | 6 | 3 | 6 | ||||
4 | 4 | 5 | 6 | 7 | 4 | 10 |
S[2][2]를 구하는 방식을 풀어보면 아래와 같다.
- S[1][2] = D[1][1] + D[1][2]
- S[2][1] = D[1][1] + D[2][1]
- S[2][2] = D[1][1] + D[1][2] + D[2][1] + D[2][2]
= S[1][2] + S[2][1] - D[1][1] + D[2][2]
이를 토대로 공식을 만드면 아래와 같이 만들 수 있다.
- S [ i ][ j ] = S [ i ][ j - 1 ] + S [ i - 1 ][ j ] - S[ i - 1 ][ j - 1 ] + D[ i ][ j ]
=> 이 상태에서 다시 위의 가.로 넘어가면 N + 1 x N + 1 크기를 만들어줄 것이므로,
S [ i ][ j ] = S [ i ][ j - 1 ] + S [ i - 1 ][ j ] - S[ i - 1 ][ j - 1 ] + D[ i ][ j ]
만약 행이 1이라면, 그 위인 S[0][N] 값은 모두 0이 될 것이므로
S [ i ][ j ] = S [ i ][ j - 1 ] + 0 - 0 + D[ i ][ j ]
= S [ i ][ j - 1 ] + D[ i ][ j ] // 나. 의 공식을 그대로 사용해도 무방하다는 걸 볼 수 있다.
마찬가지로 열이 1일때도 동일하다.
[3] 구간합 배열에서 범위값을 구하는 방법
만약에 (1, 1)에서 (3, 3)까지의 구간합을 구하라 하면 범위값을 구하는 건 쉽다.
- 그냥 S[3][3]를 반환하면 된다.
그런데 (2, 2)에서 (3, 3)까지의 구간합을 구하라 한다면?
1 | 2 | 3 | 4 | 구간합 --------> |
1 | 2 | 3 | 4 | ||
1 | 1 | 2 | 3 | 4 | 1 | 1 | 3 | 6 | 10 | |
2 | 2 | 3 | 4 | 5 | 2 | 3 | 8 | 15 | 24 | |
3 | 3 | 4 | 5 | 6 | 3 | 6 | 15 | 27 | 42 | |
4 | 4 | 5 | 6 | 7 | 4 | 10 | 24 | 42 | 64 |
S[3][3]에서 (1, 2) ~ (1, 3)과 (2, 1) ~ (2, 3) 합을 빼주고 (1, 1)값을 더해주어야 한다.
- 만약 범위를 (x1, y1) ~ (x2, y2)라고 한다면
구간합 범위를 구하는 식 : S[x2][y2] - S[x1-1][y2] - S[x2][y1-1] + S[x1 - 1][y1 -1]
⌨️ 코드
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
public class Main {
public static void main(String[] args) throws Exception{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
StringBuilder sb = new StringBuilder();
// 입력
int N = Integer.parseInt(st.nextToken()); // 행의 길이
int M = Integer.parseInt(st.nextToken()); // 케이스 수
// 기존 배열 D
int[][] D = 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++) {
D[i][j] = Integer.parseInt(st.nextToken());
}
}
// 구간합 배열 S
int[][] S = new int[N + 1][N + 1];
for (int i = 1; i <= N; i++) {
for (int j = 1; j <= N; j++) {
S[i][j] = S[i][j-1] + S[i-1][j] - S[i-1][j-1] + D[i][j];
}
}
// 케이스당 하나씩 처리
// 범위 (x1, y1) ~ (x2, y2)의 구간합 구하기
for (int i = 1; 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 sum = S[x2][y2] - S[x1-1][y2] - S[x2][y1-1] + S[x1 - 1][y1 -1];
sb.append(sum);
sb.append("\n");
}
System.out.println(sb.toString());
}
}
'Computer Science > Algorithm' 카테고리의 다른 글
[알고리즘] 정렬 (0) | 2022.08.11 |
---|---|
[알고리즘] 알고리즘 개요 (0) | 2022.08.09 |
[백준 17609] 회문 (팰린드롬) (0) | 2022.07.13 |
[백준 10434] 행복한 소수 - 소수 구하기 + 행복한 수 (0) | 2022.07.13 |
[백준 11659] 구간합 (0) | 2022.07.12 |