nashidos’s diary

アルゴリズムとか機械学習とか色々

Pythonで解くナップサック問題【動的計画法(DP)入門】

この記事では競技プログラミング等で頻出のアルゴリズムである「動的計画法」をナップサック問題を通して解説していきます。

まず動的計画法についての解説をしてから、実際にナップサック問題Pythonで解いていきます。



そもそも動的計画法(DP)とは

動的計画法を簡単に説明すると「問題を分割して計算結果を記録しながら解いていく手法」です。

細かく定義されているアルゴリズムではないので一言で動的計画法といっても種類がいくつかあります。

代表的な種類としては以下のようなものがあります。

  • ナップサックDP
  • 区間DP
  • bitDP

今回はこの中のナップサックDPを解いていきますが、難しい!と感じたらより簡単な動的計画法の問題を以下の記事で解説しているので参照してみてください。
nashidos.hatenablog.com

ナップサック問題を解こう

atcoder.jp

問題文

N 個の荷物があり、i(1≦i≦N) 番目の荷物には価値  v_i と重さ  w_iが割り当てられている。
許容重量 Wのナップサックが1つある。重さの和が W以下となるように荷物の集合を選びナップサックに詰め込むとき、価値の和の最大値を求めよ。ただし、同じ荷物は一度しか選ぶことができない。

制約
入力はすべて整数
1≤N≤100
1≤W≤105
1≤ w_i≤W
1≤ v_i ≤109

実装

問題文を読むと「価値」/「重さ」とかで順位付けをして解きたくなりますが今回はそれでは解くことができません。

そこで、動的計画法を利用して「価値」と「重さ」を記録していきます。

実際にやることは2つくらいしかありません。

  1. DPテーブルを作る
  2. 条件に応じてテーブルを更新

条件も選ぶOR選ばないの2通りなので難しいことはありません。

選ばない時、すなわち許容重量を超えてしまっているときは

 dp[i+1][w]=dp[i][w]

選ぶ時、すなわち許容重量以内であるときは

 dp[i+1][w]=max(dp[i][w],dp[i][w-weight[i]]+value[i])

以下がPythonのサンプルコードです。

N,W = map(int,input().split())
w = []
v = []
for i in range(N):
    x,y = map(int,input().split())
    w.append(x)
    v.append(y)

dp = [[0]*(W+1) for j in range(N+1)] # DPテーブルの作成

for i in range(N):
    for j in range(W+1):
        if j < w[i]: # 選ばない時
            dp[i+1][j] = dp[i][j]
        else: # 選ぶ時
            dp[i+1][j] = max(dp[i][j],dp[i][j-w[i]]+v[i])

print(dp[N][W])

実際にDPテーブルがどのように更新されていくかを観察してみるとわかりやすいと思います。

# 入力
3 8
3 30
4 50
5 60
# DPテーブルの更新の遷移
[0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 30, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0]
---------------------------
[0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 30, 30, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0]
---------------------------
[0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 30, 30, 30, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0]
---------------------------
[0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 30, 30, 30, 30, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0]
---------------------------
[0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 30, 30, 30, 30, 30, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0]
---------------------------
[0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 30, 30, 30, 30, 30, 30]
[0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0]
---------------------------
[0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 30, 30, 30, 30, 30, 30]
[0, 0, 0, 30, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0]
---------------------------
[0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 30, 30, 30, 30, 30, 30]
[0, 0, 0, 30, 50, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0]
---------------------------
[0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 30, 30, 30, 30, 30, 30]
[0, 0, 0, 30, 50, 50, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0]
---------------------------
[0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 30, 30, 30, 30, 30, 30]
[0, 0, 0, 30, 50, 50, 50, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0]
---------------------------
[0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 30, 30, 30, 30, 30, 30]
[0, 0, 0, 30, 50, 50, 50, 80, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0]
---------------------------
[0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 30, 30, 30, 30, 30, 30]
[0, 0, 0, 30, 50, 50, 50, 80, 80]
[0, 0, 0, 0, 0, 0, 0, 0, 0]
---------------------------
[0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 30, 30, 30, 30, 30, 30]
[0, 0, 0, 30, 50, 50, 50, 80, 80]
[0, 0, 0, 30, 0, 0, 0, 0, 0]
---------------------------
[0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 30, 30, 30, 30, 30, 30]
[0, 0, 0, 30, 50, 50, 50, 80, 80]
[0, 0, 0, 30, 50, 0, 0, 0, 0]
---------------------------
[0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 30, 30, 30, 30, 30, 30]
[0, 0, 0, 30, 50, 50, 50, 80, 80]
[0, 0, 0, 30, 50, 60, 0, 0, 0]
---------------------------
[0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 30, 30, 30, 30, 30, 30]
[0, 0, 0, 30, 50, 50, 50, 80, 80]
[0, 0, 0, 30, 50, 60, 60, 0, 0]
---------------------------
[0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 30, 30, 30, 30, 30, 30]
[0, 0, 0, 30, 50, 50, 50, 80, 80]
[0, 0, 0, 30, 50, 60, 60, 80, 0]
---------------------------
[0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 30, 30, 30, 30, 30, 30]
[0, 0, 0, 30, 50, 50, 50, 80, 80]
[0, 0, 0, 30, 50, 60, 60, 80, 90]

*ちなみにAtCoderで提出する時はPythonでは間に合わないのでPyPyで出しましょう。

まとめ

今回はナップサックDPを解いていきましたが、DPは応用の幅が非常に広いアルゴリズムです。

ダイクストラ法などもDPの概念を知っていればかなり理解しやすいのではないかと思います。

おわり