✔ 문제
난이도 : 골드5 🥇
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만큼의 무게만 넣을 수 있는 배낭만 들고 다닐 수 있습니다.
이때 최대한 즐거운(가치가 높은) 여행을 하기 위해 배낭에 넣을 수 있는 물건들의 가치의 최댓값을 구하는 문제입니다.
✔ 문제 풀이
대표적인 DP문제 중 하나인 "배낭(knapsack) 문제" 입니다.
배낭 문제는 짐을 쪼갤 수 있는 경우와 짐을 쪼갤 수 없는 경우로 나눌 수 있는데, 위의 문제는 짐을 쪼갤 수 없는 배낭 문제인 0/1 Knapsack Problem 이라고 합니다.
0/1 Knapsack Problem 문제는 DP 방법을 사용하여 풀어 낼수 있고 0/1이라고 붙여진 것은 각 아이템이 단 한번만 선택할 수 있기 때문입니다.
dp의 핵심은 메모이제이션을 통해 중복연산을 제거하는 것입니다.
그러기 위해서 무게(w)가 1에서부터 k까지 일때의 가치(v)를 dp배열에 기록하면서 기록된 값들을 통해 중복 연산을 하지 않고 문제를 풀 수 있습니다.
가장 직관적으로 1에서부터 k까지 배열을 채워나가는 bottom-up 방식이 가장 이해하기 쉬웠습니다.
for( int i = 1; i<=n; i++){
for( int j = 1; j<=k; j++) {
if( w[i] > j){
dp[i][j] = dp[i - 1][j];
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i]);
}
}
}
먼저 코드를 보겠습니다.
탐색하려는 물건의 무게(w[i])가 메모이제이션 할 무게(j) 보다 크다면 이 물건은 기록될 수 없기 때문에 이전값(dp[i-1][j])을 입력합니다.
만약 i가 1이고 j가 1이라면 1이라는 무게를 충족하는 물건이 없기 때문에 dp[1][1] = dp[1-1][1]이 채워지게 되고 해당하는 값이 없다면 아래 그림처럼 계속해서 0으로 채워지게 됩니다.
하지만 메모이제션 할 무게(j)를 계속 늘리면서 해당하는 무게의 물건을 찾게되면 즉, 물건의 무게가 j보다 작거나 같다면
dp[i][j] = Math.max(dp[i -1][j], dp[i-1][j - w[i]] + v[i]);
같은 무게를 만족하는 다른 물건의 가치와 ( dp[i-1][j] ),
지금 탐색중인 물건의 무게를 뺀 무게에 해당하는 물건과 탐색중인 물건의 가치를 더한 값 ( dp[i-1][j - w[i]] + v[i] )
중 더 큰 값을 해당 배열에 저장합니다.
위 그림에서 색칠한 부분을 보면 탐색중인 물건의 무게는 3이고 메모이제이션 하기위한 무게는 7입니다.
이전 물건(2)에 누적된 가치는 13입니다 이때, 탐색중인 물건(3)에 해당하는 무게는 3이기 때문에
j - w[i] 즉, 7에서 3을 뺀 무게인 4의 무게를 갖고있는 물건을 하나 더 배낭에 넣을 수 있습니다.
탐색하려는 물건의 가치와 4의 무게를 갖을 수 있는 물건의 누적된 최대 가치를 더하게 되므로써 최대의 조합을 얻을 수 있습니다.
🔎 전체 코드
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
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[] w = new int[n + 1];
int[] v = new int[n + 1];
int[][] dp = new int[n+1][k+1];
for( int i = 1; i<=n; i++){
st = new StringTokenizer(br.readLine());
w[i] = Integer.parseInt(st.nextToken());
v[i] = Integer.parseInt(st.nextToken());
}
for( int i = 1; i<=n; i++){
for( int j = 1; j<=k; j++) {
if( w[i] > j){
dp[i][j] = dp[i - 1][j];
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i]);
}
}
}
System.out.println(dp[n][k]);
}
}
📚 Reference : https://st-lab.tistory.com/141
'Algorithm > 문제' 카테고리의 다른 글
[BOJ/JAVA] 백준 1890번 점프 ( DP ) (0) | 2024.03.31 |
---|---|
[BOJ/JAVA] 백준 15486번 퇴사2 ( DP ) (0) | 2024.03.30 |
[BOJ/JAVA] 백준 14888번 연산자 끼워넣기 ( DFS, 백트래킹, 재귀호출 ) (0) | 2024.03.24 |
[BOJ/JAVA] 백준 2504번 괄호의 값 ( 스택 Stack ) (0) | 2024.03.24 |
[BOJ/JAVA] 백준 1912번 연속합 ( DP ) (0) | 2024.03.18 |