【類題】EDPC - E - Knapsack 2 - 競プロはじめました
考え方②:「ぴったり」で考える方法
「$W$以下」ではなく,「$W$ぴったり」となるように考えるのがよくあるパターン.また,ぴったりを考えたほうが,遷移が考えやすい(勘違いしにくい).【例】
$N$コから選んで重さが$W$(ぴったり)になるときの価値の最大値を$f(N, W)$とする.もし,$0\leq n \leq N -1$,$0\leq w \leq W - 1$について$f(n, w)$がわかっていれば,$f(N, W)$が計算できる.よって,DPで計算できる.具体的には,$n$コ目を選ばない場合と選ぶ場合を考えて
【貰うDP】
\begin{aligned}
f(n, w)
=\max\{f(n - 1, w), f(n - 1, w - w_{n}) + v_{n} \}
\end{aligned}
【配るDP】f(n, w)
=\max\{f(n - 1, w), f(n - 1, w - w_{n}) + v_{n} \}
\end{aligned}
\begin{aligned}
\begin{cases}
\, f(n + 1, w) = \max\{f(n + 1, w), f(n, w) \} \\
\, f(n + 1, w + w_{n})
=\max\{f(n + 1, w + w_{n}), f(n, w) + v_{n} \}
\end{cases}
\end{aligned}
である.\begin{cases}
\, f(n + 1, w) = \max\{f(n + 1, w), f(n, w) \} \\
\, f(n + 1, w + w_{n})
=\max\{f(n + 1, w + w_{n}), f(n, w) + v_{n} \}
\end{cases}
\end{aligned}
答えは
\begin{aligned}
\max \{f(N, w) \mid 0\leq w \leq W\}
\end{aligned}
である.\max \{f(N, w) \mid 0\leq w \leq W\}
\end{aligned}
貰うDP
INF = 1 << 60 N, W = map(int, input().split()) WV = [tuple(map(int, input().split())) for _ in range(N)] dp = [[-INF] * (W + 1) for _ in range(N + 1)] dp[0][0] = 0 for n in range(N): for w in range(W + 1): wn, vn = WV[n] dp[n + 1][w] = dp[n][w] if w - wn >= 0: dp[n + 1][w] = max(dp[n + 1][w], dp[n][w - wn] + vn) print(max(dp[N]))
考え方①:「以下」で考える方法
答えを求めるdpを直接作る方法.通常,ナップサック問題の回答はこっちで解かれている.$f(N, W)$を答えとする(つまり,$W$以下!).$0\leq n \leq N -1$,$0\leq w \leq W - 1$について$f(n, w)$がわかっていれば,$f(N, W)$が計算できる.よって,DPで計算できる.具体的には,$n$コ目を選ばない場合と選ぶ場合を考えて
【貰うDP】
\begin{aligned}
f(n, w) = \max\{f(n - 1, w), f(n - 1, w - w_{n}) + v_{n}\}
\end{aligned}
【配るDP】f(n, w) = \max\{f(n - 1, w), f(n - 1, w - w_{n}) + v_{n}\}
\end{aligned}
\begin{aligned}
\begin{cases}
\, f(n + 1, w) = \max\{f(n + 1, w), f(n, w) \} \\
\, f(n + 1, w + w_{n})
=\max\{f(n + 1, w + w_{n}), f(n, w) + v_{n} \}
\end{cases}
\end{aligned}
である.\begin{cases}
\, f(n + 1, w) = \max\{f(n + 1, w), f(n, w) \} \\
\, f(n + 1, w + w_{n})
=\max\{f(n + 1, w + w_{n}), f(n, w) + v_{n} \}
\end{cases}
\end{aligned}
よって,考え方②と全く同じ遷移になる.ただし,初期化の方法が異なる(-INF
ではなくゼロで初期化すれば,初期化も同じになる).
貰うDP
INF = 1 << 60 N, W = map(int, input().split()) WV = [tuple(map(int, input().split())) for _ in range(N)] dp = [[-INF] * (W + 1) for _ in range(N + 1)] dp[0] = [0] * (W + 1) for n in range(N): for w in range(W + 1): wn, vn = WV[n] dp[n + 1][w] = dp[n][w] if w - wn >= 0: dp[n + 1][w] = max(dp[n + 1][w], dp[n][w - wn] + vn) print(dp[N][W])