動的計画法 - ナップザック問題

動的プログラミング

ナップサック問題

01 バックパック問題

動的プログラミング

  • ステータス表示f[i][j]
    • コレクション: 最初の i 項目を考慮し、ボリュームが j を超えないすべての選択方法
    • 属性:マックス
  • 状態コンピューティング: コレクションの分割

N個のアイテムと容量Vのバックパックがあります。各アイテムは 1 回のみ使用できます。

i 番目の項目の体積はvi v_iですv私は,价值是 w i w_i w私は

リュックサックの容量を超えず、合計値が最大になるように、リュックサックにどのアイテムを入れるかを考えます。
最大値を出力します。

入力フォーマット

最初の行では、2 つの整数 N と V がスペースで区切られており、それぞれアイテムの数とバックパックの体積を示しています。

次に N 行があり、それぞれに 2 つの整数vi 、 wi v_i、w_i が含まれます。v私はw私はスペースで区切られた は、それぞれ i 番目のアイテムのボリュームと値を表します。

出力フォーマット

最大値を表す整数を出力します。

データ範囲

0<N,V≤10000<N,V≤1000
0<vi,wi≤10000< vi , wi v_i,w_iv私はw私は≤1000

入力サンプル
4 5
1 2
2 4
3 4
4 5
出力例:
8
二次元
  • 状態の定義 f[i][j]: 最初の i 項目、ナップザック容量 j の下での最適解 (最大値)

  • ナップザックの容量が十分な場合、i 番目のアイテムを選択するかどうかを決定する必要があります。

    • 選ばないでくださいf[i][j] = f[i-1][j]
    • 選択するf[i][j]=f[i-1][j-v[i]]+w[i]
    • 最大値を取得する方法を決定するため、上記の 2 つのケースでは次の時間がかかります。max()

    コード

    #include <iostream>
    #include <algorithm>
    using namespace std;
    const int N = 1010;
    int v[N], w[N];
    int f[N][N];
    int main() {
          
          
        int n, m;
        cin >> n >> m;
        for (int i = 1; i <= n; ++i)
            cin >> v[i] >> w[i];
        for (int i = 1; i <= n; ++i) {
          
          
            for (int j = 1; j <= m; ++j) {
          
          
                f[i][j] = f[i - 1][j];
                if (v[i] <= j)  f[i][j] = max(f[i][j], f[i -1][j - v[i]] + w[i]);
            }
        }
        cout << f[n][m];
        return 0;
    }
    
一次元

私たちが定義した状態はf[i][j]i と j の任意の合法的な最適解を取得できますが、タイトルには最終状態のみが必要なf[n][m]ので、状態を更新するために必要なのは 1 次元空間だけです。

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int v[N], w[N];
int f[N];
int main() {
    
    
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; ++i)
        cin >> v[i] >> w[i];
    for (int i = 1; i <= n; ++i) {
    
    
        for (int j = m; j >= v[i]; --j) {
    
    
            
            
            f[j] = max(f[j], f[j - v[i]] + w[i]);
        }
    }
    cout << f[m];
    return 0;
}

完全なナップサック問題

動的プログラミング

  • ステータス表示f[i][j]
    • コレクション: 最初の i 項目を考慮し、ボリュームが j を超えないすべての選択方法
    • 属性:マックス
  • 状態コンピューティング: コレクションの分割

f[i][j]i 番目のアイテムとして k 個のアイテムを選択し、最初に k 個のアイテム i を削除し、次に k 個のアイテム i を返します。

f[i][j] = f[i-1][j-v[i]*k]+w[i]*k

暴力的な二穴同時挿入

#include <iostream>

using namespace std;
const int N = 1010;
int f[N][N],v[N], w[N];
int main() {
    
    
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; ++i) 
        cin >> v[i] >> w[i];
    for (int i = 1; i <= n; ++i) {
    
    
        for (int j = 1; j <= m; ++j) {
    
    
            for (int k = 0; k * v[i] <= j; ++k) {
    
    
                f[i][j] = max(f[i][j],f[i - 1][j - k * v[i]] + k * w[i]);
                // cout << f[i][j];
            }
        }
    }
    cout << f[n][m];
    return 0;
}

私たちは知ることができます

f[i,j]=Max(f[i-1,j],f[i-1,j-v]+w,f[i-1.j-2v]+2w,...,f[i-1.j-kv]+kw

f[i,j-v]=Max( f[i-1,j-v],f[i-1.j-2v]+w,...,f[i-1.j-kv]+(k-1)w

コード

#include <iostream>
// #include <algorithm>
using namespace std;
const int N = 1010;
int f[N][N],v[N], w[N];
int main() {
    
    
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; ++i) 
        cin >> v[i] >> w[i];
    for (int i = 1; i <= n; ++i) {
    
    
        for (int j = 1; j <= m; ++j) {
    
    
            f[i][j] = f[i-1][j];
            if (j >= v[i])  f[i][j] = max(f[i][j], f[i][j-v[i]] + w[i]);           
        }
    }
    cout << f[n][m];
    return 0;
}

1次元コード

#include <iostream>
// #include <algorithm>
using namespace std;
const int N = 1010;
int f[N],v[N], w[N];
int main() {
    
    
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; ++i) 
        cin >> v[i] >> w[i];
    for (int i = 1; i <= n; ++i) {
    
    
        for (int j = v[i]; j <= m; ++j) {
    
    
            // f[i][j] = f[i-1][j];
            f[j] = max(f[j], f[j-v[i]] + w[i]);           
        }
    }
    cout << f[m];
    return 0;
}

複数のナップザックの問題

動的プログラミング

  • ステータス表示f[i][j]
    • コレクション: 最初の i 項目を考慮し、ボリュームが j を超えないすべての選択方法
    • 属性:マックス
  • 状態コンピューティング: コレクションの分割

トピックの説明

N個のアイテムと容量Vのバックパックがあります。

i 項目には最大でもsi s_i が含まれますs私は個、各個の体積はvi v_iv私は ,价值是 w i w_i w私は

アイテムの体積の合計がバックパックの容量を超えず、値の合計が最大になるように、バックパックにどのアイテムを入れるかを解決します。
最大値を出力します。

入力フォーマット

最初の行では、2 つの整数 N と V がスペースで区切られており、それぞれアイテムの数とバックパックの体積を表しています。

次に N 行があり、それぞれに 3 つの整数vi v_iが含まれます。v私はウィウィ_イw私はし、しs私はスペースで区切られた は、それぞれ i 番目のアイテムの体積、値、数量を表します。

出力フォーマット

最大値を表す整数を出力します。

データ範囲

0<N,V≤100
0< vi v_iv私はウィウィ_イw私はし、しs私は≤100

入力サンプル
4 5
1 2 3
2 4 1
3 4 3
4 5 2
出力例:
10

コード

#include <iostream>
using namespace std;
const int N = 110;
int f[N][N], w[N], v[N], s[N];
int main() {
    
    
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; ++i) 
        cin >> v[i] >> w[i] >> s[i];
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= m; ++j) {
    
    
            for (int k = 0; k * v[i] <= j && k <= s[i]; ++k) {
    
    
                f[i][j] = max(f[i][j], f[i - 1][j - k * v[i]] + k * w[i]);
            }
        }
    cout << f[n][m];
    return 0;
}

データ範囲が広がる場合

データ範囲

0<N≤1000

0<V≦2000

0< vi v_iv私はウィウィ_イw私はし、しs私は≤2000

f(i, j) = Max(f(i-1,j),f(i-1,j-v)+w,f(i-1,j-2v)+2w+...+f(i-1,j-sv)+sw)
f(i, j-v) = Max(f(i-1,j-v),f(i-1,j-2v)+w,f(i-1,j-3v)+2w+...+f(i-1,j-sv)+(s-1)w,f(i, j) = Max(f(i-1,j),f(i-1,j-v)+w,f(i-1,j-2v)+2w+...+f(i-1,j-(s+1)v)+sw)

したがって、完全なナップザック問題では解決できません

バイナリ最適化+01 ナップザック問題の手法を採用できます。

バイナリ最適化

リンゴの束と 10 個の箱がある場合、リンゴを n 個選択します。このリンゴの束を 1, 2, 4, 8, 16, ... 512 に従って 10 個の箱に分割すると、任意の数 x∈[0,1023] (11 番目の箱は 1024 を取得できるため、コメント領域があります)これについては議論) はこれら 10 個の箱の中のリンゴの数から表すことができますが、この方法での選択数は ≤ 10 倍になります

コード

#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int v[N], w[N];
int f[2020];
int main() {
    
    
    int n, m;
    cin >> n >> m;
    int cnt = 0;
    while (n--) {
    
    
        int a, b, s;
        cin >> a >> b >> s;
        int k = 1;
        while (k <= s) {
    
    
            v[++cnt] = a * k;
            w[cnt] = b * k;
            s -= k;
            k *= 2;
        }
        if (s){
    
    
            v[++cnt] = a * s;
            w[cnt] = b * s;
        }
    }
    n = cnt;
    for (int i = 1; i <=n; ++i) {
    
    
        for (int j = m; j >= v[i]; --j) {
    
    
            f[j] = max(f[j], f[j - v[i]] + w[i]);
        }
    }
    cout << f[m];
    return 0;
}

集団ナップサック問題

N セットのアイテムと容量 V のバックパックがあります。

各グループには複数の項目があり、同じグループ内で選択できる項目は最大 1 つだけです。
各項目の体積はvi , j v_{i,j}です。v j、値はwi 、 j w_{i,j}です。w jここで、i はグループ番号、j はグループ番号です。

アイテムの合計がバックパックの容量を超えず、合計値が最大になるように、どのアイテムをバックパックに入れるかを解決します。

最大値を出力します。

入力フォーマット

最初の行には、スペースで区切られた 2 つの整数 N、V があり、それぞれアイテム グループの数とバックパックの容量を表します。

次に N セットのデータがあります。

  • データの各セットの最初の行には整数S i S_{i}が含まれます。S私は、i 番目の項目グループ内の項目の数を示します。
  • 各データセットの後にはS i S_{i}が続きますS私は行。各行には 2 つの整数vi 、 j v_{i,j}が含まれます。v jwi 、 j w_{i,j}w jスペースで区切られた は、それぞれ i 番目の項目グループ内の j 番目の項目の量と値を表します。
出力フォーマット

最大値を表す整数を出力します。

データ範囲

0<N,V≤100
0<Si≤100
0< vi , j v_{i,j}v jwi 、 j w_{i,j}w jj≤100

入力サンプル
3 5
2
1 2
2 4
1
3 4
1
4 5
出力例:

8

コード

#include <iostream>
using namespace std;
const int N = 110;
int v[N], w[N];
int f[110];
int main() {
    
    
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; ++i) {
    
    
        int s;
        cin >> s;
        for (int j = 1; j <= s; ++j) {
    
    
            cin >> v[j] >> w[j];
        }
        for (int k = m; k >= 1; --k) {
    
    
            for (int j = 1; j <= s; ++j) {
    
    
                if (v[j] <= k)
                    f[k] = max(f[k], f[k - v[j]] + w[j]);
            }
        } 
    }
    cout << f[m];
    return 0;
}

おすすめ

転載: blog.csdn.net/weixin_64632836/article/details/128991820