https://www.acmicpc.net/problem/1749

이 문제는 2차원 행렬에서 가장 큰 합을 갖는 부분 행렬을 구하는 문제입니다. 주어진 행렬에서 적절한 부분 행렬을 찾아서 그 안에 있는 원소들의 합이 최대가 되는 값을 출력해야 합니다. 이 문제는 부분 배열에서의 최대 합을 찾는 카데인 알고리즘(Kadane's Algorithm)을 확장한 2차원 버전으로 해결할 수 있습니다.
import java.io.*;
import java.util.*;
public class Main {
public static void main(String[] args) throws IOException {
// 입력 처리
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
// 행렬의 크기 N (행), M (열) 입력 받기
int N = Integer.parseInt(st.nextToken());
int M = Integer.parseInt(st.nextToken());
// 행렬 입력 받기
int[][] matrix = new int[N][M];
for (int i = 0; i < N; i++) {
st = new StringTokenizer(br.readLine());
for (int j = 0; j < M; j++) {
matrix[i][j] = Integer.parseInt(st.nextToken());
}
}
// 최대 합을 저장할 변수 (초기 값은 아주 작은 음수)
int maxSum = Integer.MIN_VALUE;
// 열 구간을 정해서 1차원 배열로 변환하여 최대합 구하기
for (int left = 0; left < M; left++) {
int[] temp = new int[N]; // 각 열 구간에 대한 임시 배열
for (int right = left; right < M; right++) {
// 열 구간 (left ~ right)에 대한 합을 temp에 저장
for (int i = 0; i < N; i++) {
temp[i] += matrix[i][right];
}
// 1차원 배열에서 최대 부분합 구하기 (카데인 알고리즘)
int currentMax = kadane(temp);
maxSum = Math.max(maxSum, currentMax);
}
}
// 결과 출력
System.out.println(maxSum);
}
// 카데인 알고리즘: 1차원 배열에서 최대 부분합을 구하는 함수
public static int kadane(int[] arr) {
int maxEndingHere = arr[0];
int maxSoFar = arr[0];
for (int i = 1; i < arr.length; i++) {
maxEndingHere = Math.max(arr[i], maxEndingHere + arr[i]);
maxSoFar = Math.max(maxSoFar, maxEndingHere);
}
return maxSoFar;
}
}
N(행)과 M(열)을 입력받고, matrix[N][M] 배열에 행렬의 값을 저장합니다.left부터 right 열까지의 값을 모두 합한 부분 배열을 만듭니다.left = 0, right = 1일 때, 0번 열과 1번 열을 더한 1차원 배열이 생성됩니다.left와 right):left는 0부터 M-1까지, right는 left부터 M-1까지 반복되므로, 열 구간을 선택하는 과정의 시간 복잡도는 O(M^2)입니다.temp[] 배열에 대해 카데인 알고리즘을 적용하는데, 이 과정의 시간 복잡도는 O(N)입니다. (행의 크기만큼 처리)N = 200, M = 200일 때, 최악의 경우 200^2 * 200 = 8,000,000 연산이므로 제한 시간 내에 충분히 해결할 수 있습니다.temp[] 배열은 크기가 N이므로, 추가로 O(N)의 공간이 필요합니다.카데인 알고리즘은 연속된 부분 배열에서 최대 합을 구하는 효율적인 알고리즘입니다. 1차원 배열에서 연속된 숫자들의 부분 배열 중에서, 그 합이 가장 큰 배열을 찾는 문제를 해결합니다. 이 알고리즘은 동적 계획법(DP)의 아이디어를 기반으로, 배열을 한 번 순회하면서 최대 부분 배열 합을 구할 수 있습니다.
주어진 배열 arr에서, 연속된 부분 배열의 합 중 최대값을 구하는 문제입니다.
예를 들어, 배열 arr = [-2, 1, -3, 4, -1, 2, 1, -5, 4]가 있다고 할 때, 이 배열의 최대 부분합을 갖는 부분 배열은 [4, -1, 2, 1]이며, 그 합은 6입니다.
카데인 알고리즘은 배열의 각 요소를 차례대로 살펴보면서, 현재 요소를 포함한 부분 배열에서의 최대 합을 갱신해 나가는 방식입니다.
maxSoFar = arr[0]입니다.maxEndingHere라는 변수에 저장합니다.arr[i]에서:maxEndingHere에 arr[i]를 더한 값과 arr[i] 중 더 큰 값을 maxEndingHere로 갱신합니다. 이 과정에서, 기존의 부분 배열을 계속 이어가는 것이 더 나은지, 아니면 현재 위치에서 새로운 부분 배열을 시작하는 것이 나은지를 결정합니다.maxSoFar에 저장하며, maxSoFar와 maxEndingHere를 비교해 더 큰 값으로 갱신합니다.maxSoFar가 최종적으로 최대 부분 배열 합을 가지는 값이 됩니다.public int kadane(int[] arr) {
// 배열의 첫 번째 원소로 초기화
int maxEndingHere = arr[0];
int maxSoFar = arr[0];
// 배열의 두 번째 원소부터 순회
for (int i = 1; i < arr.length; i++) {
// 현재 원소를 포함한 최대합을 계산 (계속 이어가거나 새로운 시작)
maxEndingHere = Math.max(arr[i], maxEndingHere + arr[i]);
// 현재까지의 최대 부분 배열 합을 갱신
maxSoFar = Math.max(maxSoFar, maxEndingHere);
}
return maxSoFar; // 최대 부분 배열 합을 반환
}
배열 arr = [-2, 1, -3, 4, -1, 2, 1, -5, 4]에 대해 카데인 알고리즘을 적용해보겠습니다.
maxEndingHere = arr[0] = -2maxSoFar = arr[0] = -2maxEndingHere = max(1, -2 + 1) = 1maxSoFar = max(-2, 1) = 1maxEndingHere = max(-3, 1 + (-3)) = -2maxSoFar = max(1, -2) = 1maxEndingHere = max(4, -2 + 4) = 4maxSoFar = max(1, 4) = 4maxEndingHere = max(-1, 4 + (-1)) = 3maxSoFar = max(4, 3) = 4maxEndingHere = max(2, 3 + 2) = 5maxSoFar = max(4, 5) = 5maxEndingHere = max(1, 5 + 1) = 6maxSoFar = max(5, 6) = 6maxEndingHere = max(-5, 6 + (-5)) = 1maxSoFar = max(6, 1) = 6maxEndingHere = max(4, 1 + 4) = 5maxSoFar = max(6, 5) = 6최종적으로, 최대 부분 배열 합은 6이 됩니다.
카데인 알고리즘은 배열을 한 번만 순회하면서 최대 부분 배열 합을 찾기 때문에, 시간 복잡도는 O(n)입니다. 여기서 n은 배열의 크기입니다. 이는 매우 효율적인 알고리즘입니다.
카데인 알고리즘은 배열을 순차적으로 처리할 때, 추가적인 배열이나 데이터 구조가 필요 없고 단지 몇 개의 변수만 사용합니다. 따라서 공간 복잡도는 O(1)입니다.
for (int left = 0; left < M; left++) {
int[] temp = new int[N]; // 임시 배열로 각 행의 부분합을 저장
for (int right = left; right < M; right++) {
// 각 행의 부분합을 갱신
for (int row = 0; row < N; row++) {
temp[row] += arr[row][right];
}
// 카데인 알고리즘으로 temp 배열에서 최대 부분합 구하기
int currentMax = kadane(temp);
maxSum = Math.max(maxSum, currentMax);
}
}
이 코드는 2차원 배열을 탐색하면서 특정 구간을 1차원 배열로 변환하고, 그 1차원 배열에서 카데인 알고리즘을 사용해 최대 부분합을 구하는 방식입니다.
문제에서 우리는 2차원 행렬에서 부분 행렬을 찾고, 그 부분 행렬 내의 합이 가장 큰 값을 구하려고 합니다. 이를 효율적으로 처리하기 위해 2차원 문제를 1차원 문제로 변환하고 있습니다.
left에서 right까지 선택합니다. left는 열 구간의 시작을, right는 끝을 나타냅니다.left에서 right까지 선택한 열들을 합쳐서 1차원 배열을 만듭니다. 이 배열 temp[]는 열 구간에서 각 행의 합을 저장한 것입니다.left부터 열 right까지의 합을 같은 행의 위치에서 모두 더한 값이 temp[]에 저장됩니다.2 3 -21 -22 -23
5 6 -22 -23 -25
-22 -23 4 10 2
첫 번째 행에서 2 + 3 = 5
두 번째 행에서 5 + 6 = 11
세 번째 행에서 22 + (-23) = -45
이 구간에 대한 1차원 배열은 temp = [5, 11, -45].
첫 번째 행에서 3 + (-21) = -18
두 번째 행에서 6 + (-22) = -16
세 번째 행에서 23 + 4 = -19
이 구간에 대한 1차원 배열은 temp = [-18, -16, -19].
temp[]에 대해 최대 부분합을 구해, 전체 행렬에서 최대 부분 행렬 합을 찾습니다.for (int left = 0; left < M; left++):left는 열의 시작 구간을 의미합니다.int[] temp = new int[N];:temp[]는 N개의 행에 대해 선택된 열 구간에 대한 합을 저장하는 1차원 배열입니다.for (int right = left; right < M; right++):right는 열의 끝 구간을 의미합니다. left부터 시작해 점차 열 구간을 확장합니다.for (int row = 0; row < N; row++):left에서 right까지의 각 열의 값을 더해서, 각 행의 합을 temp[]에 저장합니다.temp[] 배열에 저장합니다.temp[]는 이제 열 구간에서 각 행의 합을 저장하고 있으므로, 카데인 알고리즘을 적용하여 이 1차원 배열에서 연속된 부분 배열의 최대 합을 찾습니다.left와 right를 선택하므로 O(M^2)입니다.따라서 전체 시간 복잡도는 O(M^2 * N)입니다.
누적합은 배열의 부분 합을 빠르게 계산하기 위해 사용하는 기법입니다. 배열을 처리할 때, 특정 구간의 합을 구해야 할 때 그 구간의 합을 빠르게 계산할 수 있도록 미리 합을 계산하여 저장하는 방식입니다.
arr = [1, 2, 3, 4]의 누적합 배열 prefixSum은 [1, 3, 6, 10]이 됩니다.[i, j]의 합은 prefixSum[j] - prefixSum[i-1]로 한 번에 계산할 수 있습니다.카데인 알고리즘은 연속된 부분 배열에서 최대 합을 구하는 알고리즘으로, 동적 계획법을 기반으로 동작합니다. 연속적인 배열 내에서 최대 합을 구할 때, 현재까지의 최대 합을 저장하고, 현재 원소를 추가할 때 계속 누적할지 아니면 새로운 배열을 시작할지를 결정하는 방식으로 최댓값을 구합니다.
| 비교 항목 | 누적합 (Prefix Sum) | 카데인 알고리즘 (Kadane's Algorithm) |
|---|---|---|
| 적용 범위 | 특정 구간의 합을 빠르게 계산하는 문제 | 연속된 부분 배열에서 최대 합을 구하는 문제 |
| 시간 복잡도 | 배열을 처리하는 데 O(n), 구간 합 구하는 데 O(1) | O(n), 배열을 한 번 순회 |
| 알고리즘 적용 대상 | 구간 합 계산 (특정 범위의 합이 자주 필요할 때) | 최대 연속 부분 배열 합 계산 |
| 구간 합 계산 | 여러 구간 합을 반복적으로 계산할 때 유리 | 구간 합 자체는 계산하지 않음 |
| 메모리 사용 | 추가 배열(누적합 배열)이 필요 | 추가 메모리 사용 없음 |
| 부분 배열 연속성 | 비연속적인 구간 합 계산 가능 | 연속된 부분 배열만 처리 |
두 알고리즘은 문제가 요구하는 특성에 따라 각각 다른 상황에서 효율적입니다.
누적합이 더 적합합니다. 배열에서 구간합을 자주 구하는 문제에서는, 미리 누적합을 계산해 두고 구간합을 즉시 계산하는 방식이 효율적입니다. 한 번의 구간합 계산에 O(1)의 시간이 걸리기 때문에 여러 구간의 합을 빠르게 계산할 수 있습니다.
예시: 배열에서 여러 구간의 합을 구하는 쿼리가 있을 때, 누적합을 사용하면 O(1) 시간에 답을 구할 수 있습니다.
카데인 알고리즘이 더 적합합니다. 카데인 알고리즘은 연속된 부분 배열에서 최대 합을 구하는 문제를 효율적으로 해결할 수 있습니다. 배열을 한 번만 순회하면 바로 결과를 얻을 수 있기 때문에 매우 효율적입니다.
예시: "연속된 부분 배열"에서 최대 합을 구해야 할 때는 카데인 알고리즘이 O(n)의 시간 복잡도로 해결할 수 있는 최적의 방법입니다.
[r1, c1]에서 [r2, c2]까지의 합을 계산하는 경우:sum[r2][c2] - sum[r1-1][c2] - sum[r2][c1-1] + sum[r1-1][c1-1];
따라서 문제의 특성에 따라 누적합과 카데인 알고리즘 중 하나를 선택하는 것이 중요합니다. 구간 합을 구하는 문제가 아니고, 연속된 부분 배열의 최대 합을 구하는 문제라면 카데인 알고리즘이 훨씬 효율적입니다.