0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【競技プログラミング】ナップサック問題をやってみた【組合せ最適化】

Last updated at Posted at 2022-08-01

1.初めに

・アルゴリズムを学ぶために、競技プログラミングを始めました。
・AOJのコースをやっていくので、その備忘録を残します。

・前回は0-1ナップサック問題を解いたので、今回は「ナップサック問題」を解いていきます。

2.ナップサック問題

2.1問題

価値が $v_i$ 重さが $w_i$ であるような $N$ 個の品物と、容量が $W$ のナップザックがあります。次の条件を満たすように、品物を選んでナップザックに入れます。

・選んだ品物の価値の合計をできるだけ高くする。
・選んだ品物の重さの総和は $W$ を超えない。
・同じ種類の品物はいくつでも選ぶことができる。(この部分が0-1ナップサックと違う!!!!!!!!)

価値の合計の最大値を求めてください。

入力

1行目に2つの整数 $N$、$W$ が空白区切りで1行に与えられます。 続く $N$ 行で $i$ 番目の品物の価値 $v_i$ と重さ $w_i$ が空白区切りで与えられます。

出力

価値の合計の最大値を1行に出力してください。

制約

$1 ≤ N ≤ 100$
$1 ≤ v_i ≤ 1,000$
$1 ≤ w_i ≤ 1,000$
$1 ≤ W ≤ 10,000$

2.2.DPテーブルを作る

基本的には前回の0-1ナップサック問題と同じ考えです。

4 8
4 2
5 2
2 1
8 3

・入力例1が↑なので、これを元にDPテーブルを作っていきます。

i:品物の番号
j:容量の大きさ
として
$$\boldsymbol{dp[i][j]=i番目までの品物を使って、容量がjを超えないようなvの最大値}$$
としてdp表を埋めていきます。(前回と同じ)

・とりあえず自力で埋めてみました。↓

i\j 0 1 2 3 4 5 6 7 8
vi↓ wi↓ 0 0 0 0 0 0 0 0 0 0
4 2 1 0 0 4 4 8 8 12 12 16
5 2 2 0 0 5 5 10 10 15 15 20
2 1 3 0 2 5 7 10 12 15 17 20
8 3 4 0 2 5 8 10 13 16 18 21



・$dp[i][j]$を埋める時にまず最初に考えるのが、「$j$と$w$の値を比較する」です。(前回と同じ)
            ↓
・次の①か②で場合分けをします。(前回と同じ)

①:i番目の品物の重さ(w)がjの値を超えてしまっていたら(j<w)

・その$i$番目の品物は使えない。(使っただけで、容量オーバーしてしまうから)(前回と同じ)
            ↓
・それまでの価値の最大値($dp[i-1][j]$)をそのまま$dp[i][j]$入れる。(前回と同じ)

②:その逆(j≥w)の場合は(前回と違う!)

・dp[i][j-w]+vを計算する。(前回はdp[i-1][j-w]+v)
            ↓
・dp[i][j-w]+vとdp[i-1][j]を比べて、値の大きい方をdp[i][j]に入れる。
・つまりdp[i][j]に必要なdp[i][j-w]をi行目から得ることができる!!!(0-1の時はi-1行目から

・例)i=4,j=7の時:(4個目までの品物を使って容量7を超えないように価値の最大値を決めるとき)↓

i\j 0 1 2 3 4 5 6 7 8
vi↓ wi↓ 0 0 0 0 0 0 0 0 0 0
4 2 1 0 0 4 4 8 8 12 12 16
5 2 2 0 0 5 5 10 10 15 15 20
2 1 3 0 2 5 7 10 12 15 17 20
8 3 4 0 2 5 8 10 13 16

以下脳内の考え・・・
4個目までの品物[価値=4、重さ=2]、[価値=5、重さ=2]、[価値=2、重さ=1]、[価値=8、重さ=3]の四個使えるなあ。
            ↓
[価値=5、重さ=2]、[価値=5、重さ=2] (これってdp[i][j-w]の時と同じ組み合わせだよなあ)[価値=8、重さ=3] (新しく使う品物)の三個の品物を使えば価値が最大=18(これが$dp[i][j-w]+v$)となる!

みたいな感じです。

・①、②を式で表すと

dp[i][j] = \left\{
\begin{array}{ll}
dp[i-1][j] & (j<w) \\
max(dp[i-1][j], dp[i][j-w]+v) & (otherwise)
\end{array}
\right.

となる。

2.3.DP表を一次元にして考える

例によってナップサック問題のdpテーブルも一次元にできます。

今度はdp[i][j-w]+vを同じi行目の値から求めることができるので、0-1ナップサック問題とは違い、jを0から右方向へdp[i][j]を埋めていきます。

・例)i=4,j=7の時:(4個目までの品物を使って容量7を超えないように価値の最大値を決めるとき)↓

i\j 0 1 2 3 4 5 6 7 8
vi↓ wi↓ 0 0 0 0 0 0 0 0 0 0
4 2 1 0 0 4 4 8 8 12 12 16
5 2 2 0 0 5 5 10 10 15 15 20
2 1 3 0 2 5 7 10 12 15 17 20
8 3 4 0 2 5 8 10 13 16

・例えば上の表では、4行目をjの小さい方から更新して、$dp[4][6]$まで更新されています。    ↓
vi↓ wi↓ i\j 0 1 2 3 4 5 6 7 8
8 3 4 0 2 5 8 10 13 16 20

       ↓
・次求めたい値(更新する値)が$dp[4][7]$だとします。
       ↓
・$dp[4][7]=18$と分かりました。( $dp[j-w]=10$で$v=8$より)
       ↓
・更新!!
       ↓

vi↓ wi↓ i\j 0 1 2 3 4 5 6 7 8
8 3 4 0 2 5 8 10 13 16 18 20

・みたいな感じで埋めていきます。


・つまり、容量jの時の価値の最大値は
$$
dp[j] = max(dp[j], dp[j-w]+v)
$$
と表すことができます。

2.3.1.実装(一次元バージョン)

# 入力値を取得
n,W=map(int,input().split())

# 1次元のdpテーブルを作る
dp=[0 for j in range(W+1)]

# v(価値)、w(重さ)を取得するために品物の個数をiとして回す
for i in range(n):
    v,w=map(int,input().split())

    # jを小きい方から逆に回す(0-1と逆)
    for j in range(w,W+1):
        dp[j]=max(dp[j],dp[j-w]+v)
print(dp[W])

参考サイト

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?