최대 한도 무게가 K로 정해진 가방 1개에 무게가 W이고 가치가 V인 N개의 물건을 담고자 할때, 가치가 최대가 되도록 담고자 한다.
당연히 하나의 물건은 가방에 한번만 담을 수 있다
1. 1차원 배열
DP[i] = 가방에 담긴 물건들의 무게 합이 i일때, 가치의 최댓값
DP[i] = max(DP[i], DP[i-w] + v)
dp = [0]*(K+1)
for w,v in bag:
for i in range(K,-1,-1):
if i-w >= 0:
dp[i] = max(dp[i], dp[i-w] + v)
print(dp[K])
주목할 부분은 무게 i = 0,1,2,3,..,K가 아니라 i = K,K-1,K-2,...,0 순으로 돌았다는 것이다
다음과 같이 순방향으로 돌면 무슨 문제가 생기는가
dp = [0]*(K+1)
for w,v in bag:
for i in range(K+1):
if i-w >= 0:
dp[i] = max(dp[i], dp[i-w] + v)
print(dp[K])
물건 1을 담았더니 dp[0] = 0, ... , dp[w] = A, ...로 업데이트 되어가지고
이제 물건 2가 무게가 w 가치가 v여서 이 물건을 담을려고 할 때,
i = w이면 dp[w] = max(dp[w], dp[0] + v)으로, 우연히 dp[0]+v > dp[w]여서 dp[w] = dp[0]+v = v로 담아졌다고 하자.
이제 i = 2w이면 dp[2w] = max(dp[2w], dp[w] + v)으로, dp[2w]와 dp[w] + v를 비교해서 물건 2를 담을지 말지 결정해야하는데,
이미 dp[w]에서 갱신할때, 물건 2를 담아버렸다.
따라서 우연히 dp[2w] < dp[w] + v이면, dp[2w]에는 물건 2를 2번이나 담게 된다
---------------------------------------------------------------------------------------------------------------------------------------------------
그러면 이번엔 무게를 먼저 for문으로 순회하고 물건을 순회한다면
dp = [0]*(K+1)
for i in range(K,-1,-1):
for w,v in bag:
if i-w >= 0:
dp[i] = max(dp[i], dp[i-w] + v)
print(dp[K])
어떤 무게 i에 대하여, 물건들을 순회해서 현재 dp의 크기를 비교한 다음, 물건을 담을 때 크기가 커지면 갱신해주는 순서
크기를 갱신한다는 것은 이미 물건 A가 담겨진 dp[i]에서 현재 보고 있는 물건 B를 담을려고 하는 것이기 때문에
이렇게 순회하면 하나의 dp[i]에 물건이 오직 하나만 담기게 된다.. 완전히 틀린 솔루션
------------------------------------------------------------------------------------------------------------------------------------------------------
그래서 dp를 1차원 배열 dp[i] = 현재 가방에 담긴 무게가 i일때, 담긴 물건 무게의 가치 합의 최댓값으로 정의하면
물건을 먼저 순회하고 무게는 역순으로 순회해줘야한다
dp = [0]*(K+1)
for w,v in bag:
for i in range(K,-1,-1):
if i-w >= 0:
dp[i] = max(dp[i], dp[i-w] + v)
print(dp[K])
2. 2차원 배열
굳이 무게를 정방향으로 순회하고 싶다면 2차원 배열을 이용할 수 있다
dp[i][j] = 물건 i까지 봤을때, 가방에 담긴 물건 무게 합이 j인 경우 가치 합의 최댓값으로 정의하면
무게를 정방향으로 순회할 수 있다
dp[i][j] = max(dp[i-1][j], dp[i-1][j-w] + v)
dp[i-1][j]는 물건 i를 담지 않은 경우, i를 담더라도 가치 합의 최댓값이 바뀌지 않아 담을 필요 없는 경우
dp[i-1][j-w] + v는 j-w 에서 물건 i를 담은 경우
1차원 배열로 했을때, 물건 i를 담는 경우 j = w에서 물건 i를 담고 나서 j = 2w에서 또 물건 i를 담게 되는 경우가 생겼는데
dp[i-1][j-w] + v는 물건 i가 안담긴 경우로 명확하게 명시할 수 있으므로 물건 i를 2번 담는 경우를 방지할 수 있다
dp = [[0]*(k+1) for _ in range(n+1)]
for i in range(1,n+1):
w,v = bag[i-1]
for j in range(k,-1,-1):
if j-w >= 0:
dp[i][j] = max(dp[i-1][j], dp[i-1][j-w] + v)
else:
dp[i][j] = dp[i-1][j]
j-w < 0인데 dp[i][j] = dp[i-1][j]를 하는 이유?
i번째 물건까지 봤을때, i번째 물건을 담지 못하면 dp[i][j] = dp[i-1][j]로 서로 같으니까
논리적으로 생각해보면 명확하다
하지 않으면 dp[i][j] = 0으로 아무것도 안담은 것이 되니까.. 틀림
3. 한번 담은 물건을 또 담을 수 있다면
6066번: Buying Hay (acmicpc.net)
H파운드의 건초를 사고 싶은데, N개의 건초가 각각 P파운드이고 비용이 C이다.
각각의 건초는 무한히 구입할 수 있으며, 적어도 H파운드의 건초를 사는데 최소 비용을 구한다면?
무게 제한이 H이며 각각의 물건이 무게가 P이고 비용이 C일때,
적어도 H 이상이 되도록 가방에 담는데, 비용이 최소가 되도록 하려면?
위 문제와 다른 점은 최대 H가 아니고 H이상이 되어도 좋다
그리고 각각의 물건을 1번 담더라도, 또 담을 수 있다.
이는 위에서 배운, "무게를 순방향으로 순회하면 물건이 중복해서 담기는 문제"를 이용한다면
이번에는 무게를 역방향이 아니라 순방향으로 순회하면 된다는 것을 알 수 있다
또한 최소 비용을 구해야하기 때문에 dp[i+w] = min(dp[i+w], dp[i] + c)로 점화식을 구해줘야겠고
또한, H이상이 되도록 담을 수 있기 때문에, 만약 현재 무게 i와 담을려는 물건의 무게 w의 합이 h를 넘는다면?
그냥 dp[h] = min(dp[h], dp[i]+c)로 해주면 되겠다.
#한번 담은 물건을 또 담을 수 있는 배낭문제
#무게 제한이 h이상인 경우
from sys import stdin
n,h = map(int,stdin.readline().split())
bag = []
for _ in range(n):
p,c = map(int,stdin.readline().split())
bag.append((p,c))
INF = 2500000000
dp = [INF]*(h+1)
dp[0] = 0
for w,c in bag:
for i in range(h+1): #무게를 순방향 i = 0,1,2,..,h으로 순회하면 이미 담은 물건을 또 담을 수 있다
if i + w <= h:
dp[i+w] = min(dp[i+w],dp[i] + c)
else:
#최소 h 이상을 담을 수 있으므로
#현재 무게 i + 담은 물건 무게 w > h이면, h로 하면 된다
dp[h] = min(dp[h], dp[i] + c)
print(dp[h])
'알고리즘 > 다이나믹 프로그래밍' 카테고리의 다른 글
다이나믹 프로그래밍을 이용한 partition minimum sum of difference of two subset(합의 차이가 최소가 되는 분할) 문제 해법 (0) | 2024.07.08 |
---|---|
가방이 여러개인 multiple 0-1 knapsack(배낭 문제) 해법 배우기 (0) | 2024.07.03 |
i < j < k < l을 O(N)에 도는 미친 다이나믹 프로그래밍 테크닉 (0) | 2024.06.26 |
다이나믹 프로그래밍으로 구하는 복수순열2 - 주어진 개수의 알파벳들만으로 만들 수 있는 문자열의 개수 (0) | 2024.06.20 |
job assignment problem으로 알아보는 비트마스킹을 이용한 다이나믹 프로그래밍 (0) | 2024.03.27 |