DataScience/백준

[백준] 12865번: 평범한 배낭

mkk4726 2023. 6. 21. 09:00

평범한 배낭문제는 배낭에 담을 수 있는 최대 무게가 정해져있고, 이 무게를 넘지 않고 물건의 가치의 합이 최대가 되도록 물건을 가방에 담는 문제다.

 

사실 굉장히 단순한 문제인데 이해하지 못하는 부분이 있어 풀이에 시간이 꽤 걸렸다.

 

이 문제는 dp(dynamic programming)로 풀 수 있는 문제이다.
dp는 큰 문제를 작은 문제로 나눠서 푸는 문제를 말한다. 
이를 위해서는 작은 문제가 반복되어야하고, 작은 문제의 최적의 결과를 이용해 큰 문제의 결과를 도출해낼 수 있어야한다.

 

여기서는 전체 배낭(최대 무게 k)에 전체 물건을 담을 수 있는 경우가 큰 문제에 해당하고,

몇 개의 물건을 작은 배낭(최대 무게 < k)에 담는게 작은 문제에 해당한다. 

 

해당 식은 다음과 같고, ( 현재 물건의 가치+ 현재물건의 무게를 뺀 만큼의 무게에서 최대 가치 )과 (그 전 행의 값)을 비교해 큰 값을 선택하는 식이다.

$knapsnack[i][j] = max(value+knapsnack[i-1][j-weight], knapsnack[i-1][j])$

 

위 식을 반복하면서 아래의 행렬을 채우는 문제가 된다.

물건의 개수가 4개이고 최대무게가 7일 때의 문제에서 다음과 같은 배열을 채우는 문제가 되겠다.

 

여기서 핵심 컨셉은, 그 전 row의 값은 최적의 값이라는 것이다.

담는 물건의 개수와 최대 무게를 늘려가면서 순차적으로 얻은 최적의 값이라는 것이다.

즉 2행 7열에서 비교한 (6+8), (13)에서 ,
8은 2개의 물건을 사용해서 최대 무게 4에서 얻을 수 있는 최대 가치를 의미하고
13은 2개의 물건을 사용해서 최대무게 7에서 얻을 수 있는 최대가치를 의미한다.

 

구현한 코드는 아래와 같다.

from sys import stdin, setrecursionlimit
setrecursionlimit(10**6) # 파이썬 최대 재귀깊이 높이기

stdin = open('12865/sample_input.txt', 'rt') # 테스트할 때, 제출할 때는 주석처리 해주기

n, k = map(int, stdin.readline().split())

things = []
for _ in range(n):
  w, v = map(int, stdin.readline().split())
  things.append((w, v))

# 계산하기 편하게 0~k까지 만들어서, index 1~k까지로 구현하기
value_list = [[0 for _ in range(k+1)] for _ in range(n+1)]

for i in range(1, n+1):
  for j in range(1, k+1):
    # 현재 물건의 가치와 무게
    value = things[i-1][1]; weight = things[i-1][0]
    
    # 아직 담을 수 없다면, 그 전 row에 있는 값 그대로 
    if (j < weight):
      value_list[i][j] = value_list[i-1][j]
    # value_list[i-1] 행은 그 전까지 구해놓은 최적의 값들을 의미
    else: 
      value_list[i][j] = max(value + value_list[i-1][j-weight], value_list[i-1][j])

print(value_list[n][k])

 


DP를 푸는 방법 중 배열을 채워나가는 방법도 있다는 사실을 기억하고 다음에 사용하자.