💻 백준 12865번: 평범한 배낭
- 문제 링크: https://www.acmicpc.net/problem/12865
- 알고리즘 분류: 다이나믹 프로그래밍, 배낭 문제
문제 소개 🧐
N개의 물건과 각 물건의 무게(W)와 가치(V)가 주어진다. 최대 K만큼의 무게를 담을 수 있는 배낭에 물건을 담아, 가치의 합이 최대가 되도록 해야 하는, 아주 유명한 배낭 문제이다.
입력 📝
첫 줄에 물품의 수 N(1 ≤ N ≤ 100)과 준서가 버틸 수 있는 무게 K(1 ≤ K ≤ 100,000)가 주어진다. 두 번째 줄부터 N개의 줄에 거쳐 각 물건의 무게 W(1 ≤ W ≤ 100,000)와 해당 물건의 가치 V(0 ≤ V ≤ 1,000)가 주어진다.
출력 📤
한 줄에 배낭에 넣을 수 있는 물건들의 가치합의 최댓값을 출력한다.
나의 풀이 과정 🌊
첫 번째 시도: 단순 DP
Idea: dp[i]
를 ‘무게 i일 때의 최대 가치’로 정의하고, dp[i] = dp[i-w] + v
와 같은 점화식을 생각했다. 하지만 이 방식은 한 번 사용한 물건을 또 사용하는 문제가 있었다.
import sys
n, k = map(int, sys.stdin.readline().split())
w_v_dict = {}
for i in range(n):
w, v = map(int, sys.stdin.readline().split())
if w_v_dict.get(w) is None:
w_v_dict[w] = v
else:
# 같은 무게 다른 value 면 더 value가 큰 값만 저장
w_v_dict[w] = max(w_v_dict[w], v)
dp = [0] * (k+1)
# dp[i] = dp[i-w] + dp[w]
for i in range(1, k+1):
# 모든 물건에 대해 비교
for w in w_v_dict.keys():
if w > i:
# 물건의 무게가 더 무거우면 건너뛰기
continue
elif i == w:
dp[i] = max(dp[i], w_v_dict[w])
if dp[i] < dp[i-w] + dp[w]:
dp[i] = dp[i-w] + dp[w]
print(dp[k])
Result: 실패 😭
두 번째 시도: 그리디 알고리즘
Idea: (가치/무게) 비율, 즉 ‘가성비’가 좋은 물건부터 담는 그리디 방식으로 접근했다. 하지만 이 방식은 부분적인 최적해가 전체적인 최적해를 보장하지 않아 실패했다.
import sys
import heapq
n, k = map(int, sys.stdin.readline().split())
w_v_list = [list(map(int, sys.stdin.readline().split())) for _ in range(n)]
ratio_list = list(map(lambda x: (x[0]/x[1], x[0], x[1]), w_v_list)) # 무게 / 밸류 비율로 리스트 만들고
r_w_v_list = []
for i in range(n):
heapq.heappush(r_w_v_list, ratio_list[i]) # 무게 / 밸류 비율 기준으로 우선순위 큐 (값이 낮을수록 챙겨야 함)
total_weight, total_val = 0, 0
while r_w_v_list:
r, w, v = heapq.heappop(r_w_v_list)
print(r, w, v)
if total_weight + w > k:
continue
total_weight += w
total_val += v
print(total_val)
Result: 실패 😭
세 번째 시도: 올바른 DP ✨
Idea: 각 물건을 한 번만 사용하도록 보장하기 위해, DP 테이블을 뒤에서부터 갱신했다. 안쪽 루프를 최대 무게 K
부터 현재 물건의 무게 w
까지 역순으로 순회하며 dp
테이블을 갱신하면, dp[i-w]
는 아직 현재 물건이 반영되지 않은 상태이므로 중복 계산을 피할 수 있다.
Code:
import sys
n, k = map(int, sys.stdin.readline().split())
w_v_list = [list(map(int, sys.stdin.readline().split())) for _ in range(n)]
dp = [0] * (k+1)
for w, v in w_v_list:
for i in range(k, w-1, -1): # 뒤에서부터 순회하여 중복 방지
dp[i] = max(dp[i] , dp[i-w] + v)
print(dp[k])
Result: 성공! ✅
배운 점 및 느낀 점 ✍️
- 냅색(Knapsack) 문제의 기본 원리와 왜 단순 그리디로 풀 수 없는지를 명확히 이해하게 되었다.
- DP 테이블을 갱신하는 순서(앞 → 뒤 vs 뒤 → 앞)에 따라 문제의 조건(물건 중복 사용 가능/불가능)을 다르게 구현할 수 있다는 점이 인상 깊었다.
- 냅색 문제란?
제한된 무게 용량을 가진 배낭에 물건들을 담을 때, 각 물건의 가치와 무게를 고려하여 최대 가치를 얻도록 물건을 선택하는 문제