감옥의 ( L )개의 칸에 각각 죄수가 들어있다. 이때, 간수를 최대 ( G )명 고용하여 감옥을 감시할 수 있다. 각 간수는 인접한 칸을 감시해야 하며, 각 칸의 탈옥 위험도는 그 칸의 탈옥력 ( C_i )와 해당 칸을 감시하는 간수가 감시하는 죄수의 수를 곱한 값이다. 감옥 전체의 탈옥 위험도는 모든 칸의 탈옥 위험도를 합한 값이다.
목표는 ( G )명의 간수를 배치하여 감옥의 탈옥 위험도를 최소화하는 것이다.
동적 계획법(DP):
비용 함수:
분할 정복 최적화:
import java.util.*;
public class Main {
private static final long INF = Long.MAX_VALUE;
private static long[] arr, pre;
private static long[][] dp;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int L = sc.nextInt(); // 감옥 크기
int G = sc.nextInt(); // 간수 수
arr = new long[L + 1];
pre = new long[L + 1];
dp = new long[G + 1][L + 1];
// 죄수의 탈옥력 입력 및 누적합 계산
for (int i = 1; i <= L; i++) {
arr[i] = sc.nextLong();
pre[i] = pre[i - 1] + arr[i];
}
// 간수 1명일 때의 초기 DP 값
for (int i = 1; i <= L; i++) {
dp[1][i] = pre[i] * i;
}
// DP 계산 (Divide and Conquer Optimization 사용)
for (int g = 2; g <= G; g++) {
computeDP(g, 1, L, 0, L - 1);
}
// 결과 출력
System.out.println(dp[G][L]);
}
// 범위 [i, j]의 비용 계산 함수
private static long cost(int i, int j) {
return (pre[j] - pre[i]) * (j - i);
}
// DP를 분할 정복 최적화 방식으로 계산
private static void computeDP(int g, int left, int right, int optL, int optR) {
if (left > right) return;
int mid = (left + right) / 2; // 중앙 인덱스
dp[g][mid] = INF; // 초기값 설정
int bestK = -1;
// 최적 분할점 탐색
for (int k = optL; k <= Math.min(optR, mid - 1); k++) {
long currentCost = dp[g - 1][k] + cost(k, mid);
if (dp[g][mid] > currentCost) {
dp[g][mid] = currentCost;
bestK = k;
}
}
// 왼쪽, 오른쪽 서브 구간 재귀적으로 계산
computeDP(g, left, mid - 1, optL, bestK);
computeDP(g, mid + 1, right, bestK, optR);
}
}
누적합 계산:
DP 배열 정의:
비용 계산:
분할 정복 최적화:
이 문제는 DP와 분할 정복 최적화를 결합하여 대규모 데이터에서도 효율적으로 해결할 수 있다. DP 배열의 크기를 줄이고, 최적화를 통해 복잡도를 크게 감소시켰다. 구현 과정에서 최적화를 위해 비용 계산과 메모리 사용을 신중히 관리한 점이 핵심이다.