#2228 구간 나누기
난이도 : 골드 5
유형 : DP / 누적합
▸ 문제
N(1≤N≤100)개의 수로 이루어진 1차원 배열이 있다. 이 배열에서 M(1≤M≤⌈(N/2)⌉)개의 구간을 선택해서, 구간에 속한 수들의 총 합이 최대가 되도록 하려 한다. 단, 다음의 조건들이 만족되어야 한다.
- 각 구간은 한 개 이상의 연속된 수들로 이루어진다.
- 서로 다른 두 구간끼리 겹쳐있거나 인접해 있어서는 안 된다.
- 정확히 M개의 구간이 있어야 한다. M개 미만이어서는 안 된다.
N개의 수들이 주어졌을 때, 답을 구하는 프로그램을 작성하시오.
▸ 입력
첫째 줄에 두 정수 N, M이 주어진다. 다음 N개의 줄에는 배열을 이루는 수들이 차례로 주어진다. 배열을 이루는 수들은 -32768 이상 32767 이하의 정수이다.
▸ 출력
첫째 줄에 답을 출력한다.
문제 풀이
N개의 수로 이루어진 배열에서 M개 구간을 나눠 구간합의 최댓값을 구하는 문제이다.
📚 조건
- N ( 1 <= N <= 100)
- M ( 1 <= M <=⌈(N/2)⌉
- 배열에 들어가는 수의 범위 ~32768 ~32767
- 구간이 겹쳐있거나 인접해있어서는 안된다.
하향식 풀이로 구간이 M개를 나누어지는 순간 최대의 합을 가지는 구간합을 구하면 된다.
누적합을 통해 구간 합 구하기
1. 주어진 구간 데이터를 누적 합으로 저장
dp배열로 다룰 변수로는 배열 인덱스 idx, 구간 수 section으로 지정하고 입력받은 배열 값들을 누적합 배열로 저장한다.
sum = new int[n+1];
for(int i=1; i<n+1; i++) {
sum[i] = sum[i-1] + Integer.parseInt(br.readLine());
}
2. dp배열 초기값 설정
배열에 들어가는 수의 범위가 -32768 ~32767이기때문에 처음으로, Init값을 -33333으로 잡아버린 나는 당연히 음수 누적합이 -33333이 넘을 수 있다는 사실을 간과해버리고 있었다. 그러면 누적합의 값이 더 작을 수 있으므로 초기화 값이 될 수 없다.
그래서 최솟값 -32768과 n의 최대 크기 100을 곱하여 Init = -32768*100; 로 초기값을 설정해줬다.
3. 구간 합의 최댓값 구하기
완전탐색이 진짜 다른 조건들 하나도 생각안하고 그냥 top-down 재귀를 사용하면 되니깐 너무 편하다.
아 그런데, 누적합이라서 재귀 위에 for문으로 감싸주었던 부분은 살짝 고민이 필요한 부분이었다.
- idx가 구간 section에 포함되지 않은 경우 idx-1의 최댓값과 동일하다.
- dp[idx][section] = solve(idx-1, section);
- idx가 구간 section에 포함되는 경우 이제 다음 구간을 for문을 돌려 탐색하면 된다.
- 구간은 겹쳐있거나 인접해있어서는 안된다. 그래서 다음 재귀함수는 한 칸 뛰어서 i-2부터 탐색해주면 된다.
- dp[idx][section] = max (dp[idx][section], max(dp[i-2][section-1]) + sum(arr[i] ~ arr[n]));
dp[idx][section] = solve(idx-1, section);
for(int i = idx; i>0; i--) {
dp[idx][section] = Math.max(dp[idx][section], solve(i-2, section-1)+(sum[idx]-sum[i-1]));
}
3-1. 방문 여부 체크
마지막으로, 음수로 뒤덮인 누적합 배열에서 방문을 했는지 안했는지 int값으로 판단하는 것은 거의 불가능했다.
if(dp[idx][section] ≠ Init) return dp[idx][section]; 이런 조건들은 그냥 가볍게 무시되고 시간초과가 발생하였다.
그래서 결국 그래프 탐색할 때 처럼, boolean함수로 처리하였다.
if(checked[idx][section]) return dp[idx][section];
이와 같은 풀이로 예제를 돌리면 dp배열의 값은 다음과 같다.
배열 arr | -1 | 3 | 1 | 2 | 4 | -1 |
누적합 sum | -1 | 2 | 3 | 5 | 9 | 8 |
구간 개수 \ 배열 idx | 1 | 2 | 3 | 4 | 5 | 6 |
1 | -1 | 3 | 4 | 6 | Init | Init |
2 | Init | Init | 0 | 5 | 9 | 9 |
풀이 코드
import java.io.*;
import java.util.Arrays;
import java.util.StringTokenizer;
public class Main {
static int n,m;
static int[] sum;
static int[][] dp;
static boolean[][] checked;
static int Init = -32768*100;
public static void main(String[] args) throws IOException{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
n = Integer.parseInt(st.nextToken());
m = Integer.parseInt(st.nextToken());
sum = new int[n+1];
for(int i=1; i<n+1; i++) {
sum[i] = sum[i-1] + Integer.parseInt(br.readLine());
}
// - 32768 ~ 32767
dp = new int[n+1][m+1];
checked = new boolean[n+1][m+1];
for(int i=0; i<n+1; i++) {
Arrays.fill(dp[i], Init);
}
System.out.println(solve(n,m));
}
static int solve(int idx, int section) {
if(section == 0 ) return 0;
if(idx < 0 ) return Init;
if(checked[idx][section]) return dp[idx][section];
checked[idx][section] = true;
dp[idx][section] = solve(idx-1, section);
for(int i = idx; i>0; i--) {
dp[idx][section] = Math.max(dp[idx][section], solve(i-2, section-1)+(sum[idx]-sum[i-1]));
}
return dp[idx][section];
}
}
'Dot Algo∙ DS > PS' 카테고리의 다른 글
[BOJ] 백준 17472번 다리 만들기 2 (Java) (0) | 2021.06.10 |
---|---|
[BOJ] 백준 17142번 연구소 3 (Java) (0) | 2021.06.09 |
[BOJ] 백준 1963번 소수 경로 (Java) (0) | 2021.06.08 |
[BOJ] 백준 13302번 리조트 (Java) (0) | 2021.06.08 |
[BOJ] 백준 2342번 Dance Dance Revolution (Java) (0) | 2021.06.08 |