Knapsack Problem
概要
以下のような問題
-
\(N\) 種類の品物がある
-
\(i\) 番目の品物の価値は \(v_i\), 容量は \(w_i\), 個数は \(c_i\)
-
重さの総和 \(W\) まで入るナップサックに入れる
-
ナップサックに入る品物の価値を最大化する
0-1ナップサック問題: \(O(NW)\)
制約の一例は以下
基本的な制約。
一次配列を用いたdpで、インデックス逆から回す。
# i番目の重みws[i], 価値vs[i]
def solve(N, W, ws, vs):
dp = [0] * (W+1)
for i in range(N):
# 価値v, 重さw
v = vs[i]; w = ws[i]
for j in range(W, w-1, -1):
dp[j] = max(dp[j-w] + v, dp[j])
return max(dp)
Verified
-
AOJ: "DPL_1_B: Combinatorial - 0-1 Knapsack Problem": source (Python3, 0.52sec)
個数制限なしナップサック問題: \(O(NW)\)
制約の一例は以下
一次配列を用いたdpで、インデックス順に回せばよい。
# i番目の重みws[i], 価値vs[i]
def solve(N, W, ws, vs):
dp = [0] * (W+1)
for i in range(N):
# 価値v, 重さw
v = vs[i]; w = ws[i]
for j in range(w, W+1):
dp[j] = max(dp[j-w] + v, dp[j])
return max(dp)
Verified
-
AOJ: "DPL_1_C: Combinatorial - Knapsack Problem": source (Python3, 0.55sec)
個数制限付きナップサック問題: \(O(NW)\)
制約の一例は以下
dequeを用いたSliding Window Maximumを利用した解法
各 \(0 \le k < w_i\) ごとに \(m_i\) 個前までの価値の最大値を管理しながら、重さが \(k + w_i \cdot j\) の時の価値の最大値を計算する。
from collections import deque
# i番目の重みws[i], 価値vs[i], 個数ms[i]
def solve(N, W, ws, vs, ms):
S = [0]*(W+1)
for i in range(N):
# 価値v, 重さw, 個数m
v = vs[i]; w = ws[i]; m = ms[i]
for k in range(w):
# 0 ≤ k ≤ w-1
que = deque()
for j in range((W - k) // w + 1):
# 各 p = k + j*w について計算
# 重さpの価値を重さkを基準にして管理するため、(価値v) × j を引く
# (重さpの価値は重さkのものにi番目の品物をj個入れたものとみなす)
v0 = S[k + j*w] - j*v
while que and que[-1][1] <= v0:
que.pop()
que.append((j, v0))
# 重さp の価値の最大 = (区間内について、重さkを基準にした価値の最大) + (価値v) × j
S[k + j*w] = que[0][1] + j*v
# m個前の要素を取り除く
if que and que[0][0] <= j-m:
que.popleft()
return max(S)
Verified
-
AOJ: "DPL_1_G: Combinatorial - Knapsack Problem with Limitations": source (Python3, 1.46sec)
個数制限付きナップサック問題: \(O(NW \log \max_i c_i)\)
各品物を \(c_i = 2^0 + 2^1 + ... + 2^{k-1} + 2^k + R\) (\(R \le 2^{k+1}\)) という感じに分け、
分けた品物ごとに1つの品物(\(A\) 個の品物は 重さ \(w_i \cdot A\), 価値 \(v_i \cdot A\) の1つの品物)とみなすことで、
品物が \(O(N \log \max_i c_i)\) 個の0-1ナップサック問題として解くことができる。
def solve(N, W, ws, vs, ms):
vs0 = []; ws0 = []
for i in range(N):
v = vs[i]; w = ws[i]; m = ms[i]
b = 1
while b <= m:
vs0.append(v * b)
ws0.append(w * b)
m -= b
b <<= 1
if m:
vs0.append(v * m)
ws0.append(w * m)
dp = [0] * (W+1)
N0 = len(vs0)
for i in range(N0):
v = vs0[i]; w = ws0[i]
for j in range(W, w-1, -1):
dp[j] = max(dp[j-w] + v, dp[j])
return max(dp)
Verified
-
AOJ: "DPL_1_G: Combinatorial - Knapsack Problem with Limitations": source (Python3, 4.32sec)