백준 12865번: 평범한 배낭 (JAVA)
https://www.acmicpc.net/problem/12865
12865번: 평범한 배낭
첫 줄에 물품의 수 N(1 ≤ N ≤ 100)과 준서가 버틸 수 있는 무게 K(1 ≤ K ≤ 100,000)가 주어진다. 두 번째 줄부터 N개의 줄에 거쳐 각 물건의 무게 W(1 ≤ W ≤ 100,000)와 해당 물건의 가치 V(0 ≤ V ≤ 1,000)
www.acmicpc.net
문제
이 문제는 아주 평범한 배낭에 관한 문제이다.
한 달 후면 국가의 부름을 받게 되는 준서는 여행을 가려고 한다. 세상과의 단절을 슬퍼하며 최대한 즐기기 위한 여행이기 때문에, 가지고 다닐 배낭 또한 최대한 가치 있게 싸려고 한다.
준서가 여행에 필요하다고 생각하는 N개의 물건이 있다. 각 물건은 무게 W와 가치 V를 가지는데, 해당 물건을 배낭에 넣어서 가면 준서가 V만큼 즐길 수 있다. 아직 행군을 해본 적이 없는 준서는 최대 K만큼의 무게만을 넣을 수 있는 배낭만 들고 다닐 수 있다. 준서가 최대한 즐거운 여행을 하기 위해 배낭에 넣을 수 있는 물건들의 가치의 최댓값을 알려주자.
입력
첫 줄에 물품의 수 N(1 ≤ N ≤ 100)과 준서가 버틸 수 있는 무게 K(1 ≤ K ≤ 100,000)가 주어진다. 두 번째 줄부터 N개의 줄에 거쳐 각 물건의 무게 W(1 ≤ W ≤ 100,000)와 해당 물건의 가치 V(0 ≤ V ≤ 1,000)가 주어진다.
입력으로 주어지는 모든 수는 정수이다.
출력
한 줄에 배낭에 넣을 수 있는 물건들의 가치합의 최댓값을 출력한다.
풀이
풀이 1. 2차원 dp 배열 사용
dp[i][j]를 1, 2, ... , i번째 물건을 무게 j 이하만큼 담아 만들 수 있는 최대 가치라고 정의하자. 그러면,
dp[i][j] = max(dp[i-1][j-weight[i]], dp[i-1][j])가 된다. 그리고
j < weight[i]라면, i번째 물건은 못 담지만 1, 2, ... , j-1번째 물건을 담을 수도 있으니 dp[i][j] = dp[i-1][j]가 된다.
풀이 2. 이를 1차원 dp로 압축하기
dp[j]를 '무게 j 이하를 담아 만들 수 있는 최대 가치'라고 하자. 1번째 물건을 담은 후, 2번째 물건을 담을 때 j = K에서부터 j = weight[i]까지 돈다. 거꾸로 돌면 한 물건을 2번 이상 중복하여 담는 경우를 없앨 수 있다.
https://st-lab.tistory.com/141 이 분의 풀이를 강력추천하는 바이다. 엄밀하면서도 자세하게 작성하였다.
2차원 dp에서 직전 행의 값만 참조하는 경우 2차원 dp를 1차원으로 압축할 수 있다. 다만, 이를 인지하는 것보다는 일단 dp 문제를 해결하는 것이 중요하므로, 2차원이든 3차원이든 어떻게든 풀이부터 생각하고 압축을 생각하도록 하자.
자잘한 팁으로, K의 범위를 1 이상 K+1 미만으로 하는 것은 '무게가 K까지이니' 약간 자명한 느낌이 있지만, N의 범위도 직전 index를 참조하는 연산이 있으므로 1이상 N+1 미만으로 하는 것이 편하다. 그렇게 하면 N==0인 경우를 따로 구현하지 않아도 되어, 코드 작성이 살짝 편해진다. 또한, 각 for문 단계마다 Arrays.toString(dp)를 출력해보면 과정을 콘솔에서 바로 볼 수 있다.
틀린 이유 및 배운 점
dp[i][j] = dp[i-1][j]를 빼먹어서 한번 틀렸다: dp[i][j]의 조건을 정확히 생각하면 흘리지 않았을 조건이니, dp의 조건에 대해서 더 자세히 생각할 필요가 있다 (4월 4일 추가: 다시 풀 때 또 까먹어서 틀렸다... 사람이 안 변해~)
정답 코드
1. 2차원 DP
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.StringTokenizer;
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());
int N = Integer.parseInt(st.nextToken());
int K = Integer.parseInt(st.nextToken());
int[][] dp = new int[N+1][K+1];
int[] value = new int[N+1];
int[] weight = new int[N+1];
for (int i = 1; i <= N; i++) {
st = new StringTokenizer(br.readLine());
weight[i] = Integer.parseInt(st.nextToken());
value[i] = Integer.parseInt(st.nextToken());
}
for (int i = 1; i <= N; i++) {
for (int j = 1; j <= K; j++) {
if (j < weight[i]) {
dp[i][j] = dp[i-1][j];
}
else {
dp[i][j] = Math.max(dp[i-1][j-weight[i]] + value[i], dp[i-1][j]);
}
}
//System.out.println(Arrays.toString(dp[i]));
}
System.out.println(dp[N][K]);
}
}
2. 1차원 DP
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.StringTokenizer;
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());
int N = Integer.parseInt(st.nextToken());
int K = Integer.parseInt(st.nextToken());
int[] dp = new int[K+1];
int[] value = new int[N+1];
int[] weight = new int[N+1];
for (int i = 1; i <= N; i++) {
st = new StringTokenizer(br.readLine());
weight[i] = Integer.parseInt(st.nextToken());
value[i] = Integer.parseInt(st.nextToken());
}
for (int i = 1; i <= N; i++) {
for (int j = K; j >= weight[i]; j--) {
dp[j] = Math.max(dp[j-weight[i]] + value[i], dp[j]);
}
}
System.out.println(dp[K]);
}
}