C++競プロ学習日記(仮)

( 学習記録であり解説Blogではないです )

いろいろなDP|動的計画法|ナップザック

DP 配列を- INF で埋めた DP が解らなかったのでナップザック問題を使って比較してみました。

使う問題はこれナップザック問題 | 動的計画法 | Aizu Online Judge


① 普通に普通の  0 埋めDP

まずは普通に本 *1 で学習したナップザックで解きます。

定義

dp[ i 個の荷物を考慮して ][ 重さが j 以下のとき ] := 達成可能な価値の和の最大値

 i = 0, j \geq 0 のとき

 0 個の荷物を考慮したとき、ナップザックには何も入っていないので重さ  0 ,価値の和も  0 となる。
重さ  0 ,価値  0 の状態は任意の  j に対して「重さ  j 以下」の条件を満たすため、任意の  j に対して dp[ 0 ][ j ] := 0 となる。

 i > 0, j \geq w_{i-1} のとき

 i - 1 番目の荷物の重さ  w_{i-1}j 以下の場合、ナップザックに  i - 1 番目の荷物を詰め込むことができると考え、 i - 1 番目の荷物をナップザックに入れる場合入れない場合を比較し、より価値の和が大きい方を最大値として更新していきます。

入れる場合
 i 番目の荷物を除外し、 i - 1 番目までの荷物のみで、重さ  j - w_{i-1} 以下のときの価値の最大値が得られるとすると、そこに  i 番目の荷物の重さ  w_{i-1} を加えると重さ  j 以下のときの価値の最大値の候補が得られる。

入れない場合
 i 番目の荷物を除外し、 i - 1 番目までの荷物のみで、重さ  j - w_{i-1} 以下のときの価値の最大値が得られるとすると、そこに  i 番目の荷物を加えない場合の価値の最大値は、 i 番目の荷物を除外した価値の和の最大値と同じになる。

 i > 0, j < w_{i-1} のとき

 i - 1 番目の荷物の重さ  w_{i-1} j を超える場合、「重さ  j 以下のとき」という条件を満たさないので、上記の入れない場合と同様に  i 番目の荷物を除外した価値の和の最大値と同じになる。

以上の計算が終わると dp[ N ][ W ] で定義のとおり価値の和の最大値が求まります。

漸化式

\begin{equation}
dp(0,j) = 0\\
dp(i, j)= \left \{
    \begin{array}{l}
        \mathrm {\rm max}\{dp( i - 1, j ), dp( i - 1, j - w_{i-1} ) + v_{i-1} \}&( j \geq w_{i-1} )\\
        dp( i - 1, j )&( j < w_{i-1} )\\
    \end{array}
\right.
\end{equation}
 

コード
#include <bits/stdc++.h>
using namespace std;
#define REP(i,n) for(int i=0; i<(n); i++)
struct cww{cww(){ios::sync_with_stdio(false);cin.tie(0);}}star;
int main()
{
    int N, W;
    cin >> N >> W;
    vector<int> v( N ), w( N );
    REP( i, N )
    {
        cin >> v[ i ] >> w[ i ];
    }
    vector<vector<int>> dp( N + 1, vector<int>( 100001 ) );
    for( int i = 1; i <= N; ++i )
    {
        for( int j = 0; j <= W; ++j )
        {
            if( j >= w[ i - 1 ] )
            {
                dp[ i ][ j ] = max( dp[ i - 1 ][ j ], dp[ i - 1 ][ j - w[ i - 1 ] ] + v[ i - 1 ] );
            }
            else
            {
                dp[ i ][ j ] = dp[ i - 1 ][ j ];
            }
        }
    }
    cout << dp[ N ][ W ] << endl;
    return 0;
}

 


② -INF で埋めるDP

次に、DP配列を予め -INF で埋めるDPを考えます。

定義
  • dp[ i 個の荷物を考慮して ][ 重さが j の時 ] := 達成可能な価値の和の最大値
  • 達成可能か否かを区別できるように、DP 配列を予め解に成り得ない値で埋めておく。
    価値の和の最大値となるような値が存在しない場合は解として成り得ない値になっていてほしいので、この問題の場合、制約を見ると、 N \leq 100, v_i \leq 1,000 なので、 価値の最大値 1,000 100 回足しても非負の整数にならない  -10^5 より小さな値で初期化します。
 i = 0,j = 0 のとき

①のケースと同様に, 0 個の荷物で実現可能な唯一の状態は重さ  0 価値  0 である。
従って、dp[ 0 ][ 0 ] := 0 となる。

 i = 0,j > 0 のとき

 j > 0 なる  j については, 0 個の荷物では正の重さを実現できないため、実行不可能な状態である。
よって、重さとして有り得ない値(かつ、特別扱いせずに計算を進めても有り得ない値のままでいてくれる)-INF で初期化する。

 i > 0,j \geq w_{i-1} のとき

 i - 1 番目の荷物の重さ  w_{i-1}j 以下の場合、ナップザックに荷物を詰め込むことができると考え、 i - 1 番目の荷物をナップザックに入れる場合入れない場合を比較し、より価値の和が大きい方を最大値として更新していきます。

入れる場合
 i 番目の荷物を除外し、 i - 1 番目までの荷物のみで、重さ  j - w_{i-1} のときの価値の最大値が得られるとすると、そこに  i 番目の荷物の重さ  w_{i-1} を加えると重さ  j のときの価値の最大値が得られる。

入れない場合
 i 番目の荷物を除外し、 i - 1 番目までの荷物のみで、重さ  j - w_{i-1} のときの価値の最大値が得られるとすると、そこに  i 番目の荷物を加えない場合の価値の最大値は、 i 番目の荷物を除外した価値の和の最大値と同じになる。

 i > 0, j < w_{i-1} のとき

 i - 1 番目の荷物の重さ  w_{i-1} j を超える場合、「重さ  j のとき」という条件を満たさないので、上記の入れない場合と同様に  i 番目の荷物を除外した価値の和の最大値と同じになる。

以上の計算が終わると、 N 個全ての荷物を考慮した各重さ j のときの価値の和の最大値が dp[ N ][ j ] で得られますので、dp[ N ][ j ] の行の MAX を取ると答えが求まります。

漸化式

\begin{equation}
dp(0, j)= \left \{
    \begin{array}{l}
        0&( j = 0 )\\
        -\infty&( j > 0 )\\
    \end{array}
\right.
\end{equation}

\begin{equation}
dp(i, j)= \left \{
    \begin{array}{l}
        \mathrm {\rm max}\{dp( i - 1, j ), dp( i - 1, j - w_{i-1} ) + v_{i-1} \}&( j \geq w_{i-1} )\\
        dp( i - 1, j )&( j < w_{i-1} )\\
    \end{array}
\right.
\end{equation}

コード
#include <bits/stdc++.h>
using namespace std;
#define REP(i,n) for(int i=0; i<(n); i++)
#define ALL(n) begin(n),end(n)
struct cww{cww(){ios::sync_with_stdio(false);cin.tie(0);}}star;
const int INF = 10000000;
int main()
{
    int N, W;
    cin >> N >> W;
    vector<int> v( N ), w( N );
    REP( i, N )
    {
        cin >> v[ i ] >> w[ i ];
    }
    vector<vector<int>> dp( N + 1, vector<int>( 10001, -INF ) );
    dp[ 0 ][ 0 ] = 0;
    for( int i = 1; i <= N; ++i )
    {
        for( int j = 0; j <= W; ++j )
        {
            if( j >= w[ i - 1 ] )
            {
                dp[ i ][ j ] = max( dp[ i - 1 ][ j ], dp[ i - 1 ][ j - w[ i - 1 ] ] + v[ i - 1 ] );
            }
            else
            {
                dp[ i ][ j ] = dp[ i - 1 ][ j ];
            }
        }
    }
    cout << *max_element( ALL( dp.back() ) ) << endl;
    return 0;
}

コード:https://wandbox.org/permlink/RK5gfohRlQwd7cKE



まとめ

普通の DP と INF 埋め DP の違う点
  • ①の DP が  j 以下の価値の最大値を求めるのに対し、② の -INF で埋める DP は 重さがちょうど  j の時の価値の和の最大値を求めるという違いがある事が判った。
  • また、それにより計算後の解の得方にも違いがある事が判った。



*1:TLE本など