動的計画法のナップサック問題の分析

バックパック問題の概要:

N個のアイテムと容量Vのバックパックがあります。各アイテムには、ボリュームと値の2つの属性があります。i番目のアイテムのボリュームはviで、値はwiです。バックパックに詰めるアイテムを見つけて、これらのアイテムの総量がバックパックの容量を超えないようにし、合計値が最大になるようにします。
01バックパック問題:各アイテムは最大で1回しか使用できません。0回または1回
完全なナップサック問題:各アイテムには無限の選択肢があります。最大の合計値を確保するために可能な限り入れます。
複数のナップサック問題:各アイテムの数は異なり、最大でsiです。
バックパックのグループ化の問題:アイテムのグループがN個あり、各グループにいくつかのアイテムがあります。各グループは最大で1つのアイテムしか選択できない
ため、バックパックの問題を見つけることができます。本質的には、たくさんのアイテムを提供することです。アイテムにはボリュームと価値があり、一部には数の制限があります。バックパックがあります。容量に限りがあります。バックパックを収納できる
ことを前提に梱包できる合計金額はいくらですか?注:ここでバックパックを埋める必要はありません。

01バックパック問題

問題の説明

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

i番目のアイテムのボリュームはviで、値はwiです。

バックパックに詰めるアイテムを見つけて、これらのアイテムの総量がバックパックの容量を超えないようにし、合計値が最大になるようにします。
最大値を出力します。

入力形式
最初の行2つの整数NとVはスペースで区切られ、それぞれアイテムの数とバックパックの容量を示します。

次に、スペースで区切られた2つの整数viとwiを持つN行があり、i番目のアイテムのボリュームと値を示します。

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

データ範囲
0 <N、V≤10000
<vi、wi≤1000
入力例

4 5
1 2
2 4
3 4
4 5

サンプル出力:

8

問題解決のアイデア


動的計画法の問題を理解するための最良の方法:私たちは一般に、上記の2つの観点から動的計画法を検討します**。1つは状態表現であり、もう1つは状態計算**です。いわゆる状態は一般に未知数です。ナップサック問題は2次元のf(i、j)です。状態表現は、問題全体を考え、いくつかの次元で表現する必要があるということです。ナップサック問題は一般的に二次元。状態計算とは、各状態f(i、j)を段階的に計算する方法を指します。同時に、動的計画法の問題は通常、最適化を考慮する必要があります。dp動的計画法の最適化は、一般に、動的計画法コードまたは動的計画法の状態遷移方程式の同等の変更です最初に基本的なフォームを書き、次に最適化する必要があります。ここでのナップサック問題は、実際には1次元問題に最適化できます。

状態表現は、2つの角度から考えることができます。1つはコレクションであり、もう1つはコレクションの属性ですそれぞれの私たちの状態は、実際にセットを表し、私たちはどの集合F(i、j)を考慮する必要があり、例えば、ナップザック問題のすべての選択方法のセットを表します。f(i、j)は集合を表しますが、集合の特定の属性である数値を格納します属性には、一般に3つのタイプがあります。セット内の最大値(ナップサック問題の最大値など)、最小値(最小コストの合計)、および要素の数です
01ナップサック問題では、セット一連の選択方法、つまりどの項目を選択するかを表します。つまり、すべての選択方法のコレクションです。このセットは2つの条件を満たす。最初の条件は最初のiから選択されたアイテムのみを考慮することであり、2番目の条件は選択されたアイテムの総量が<= jであることです。これら2つの条件を満たすすべてのオプションのセット。属性は、セット内のすべてのオプションの最大値です
一文の説明は次のとおりです。f(i、j)は、最初のi個のアイテムからのみ選択された合計ボリューム<= jのすべての選択方法のコレクションを表し、格納される数はの合計値の最大値です。セット内の各選択方法

状態計算では、各状態f(i、j)の計算方法を考慮する必要があります。その前に、それについて考えることができます。すべてのf(i、j)を計算すると、答えはf(N、V)になります。つまり、最初のN項目から選択されたすべての選択方法のセットであり、合計ボリュームがVを超えない場合、f(N、V)はセット内の選択方法の合計値の最大値を表します。
状態の計算は、通常、セットの分割に対応します。f(i、j)と言えば、状態計算では、現在のセットをいくつかの小さなサブセットに分割する方法を実際に検討しているため、各サブセットは前の小さな状態で表すことができますバックパックの除算は古典的な除算です。f(i、j)のセットを2つのサブセットに分割できます。実際、f(i、j)で表されるすべての選択方法は、2つのカテゴリに分類されます。1つはiを含まない選択方法であり、もう1つはiを含む選択方法ですサブセット分割要件を見逃すことはなく、要件の数が繰り返されないこともあります。
iなしの選択方法は、1からiまで選択されたすべてのコレクションを指し、i番目の要素を含まず、合計ボリュームはjを超えません。これは、1からi-1までのすべての選択肢のセットに相当し、合計ボリュームはjを超えません。f(i-1、j)を使用できますそして、ここでの最大の値は、f(i-1、j)
にiの選択方法が含まれていることです。これは、1からiまでのすべての選択方法のコレクションを指し、総量はjを超えず、また、 iの選択方法。この最大値を見つける方法は?ここでのすべての選択方法にはi番目の項目が含まれているため、最も価値のある選択方法を見つけることが目標です。これにより、すべてのi番目の項目を削除し、各選択方法でi番目の項目を削除できます。それは私たちの最大値が誰であるかに影響を与えません。(クラスのすべての生徒が最終成績に10ポイントを追加した場合でも、前部で最も高い人が後部で最も高くなります)。このとき、1からi-1までの選択になります。i番目のアイテムの総量はjを超えないため、i番目のアイテムの総量はj-viを超えません。つまり、1からi-1までを選択し、総量はj-viなどの選択方法のセットを超えません。つまり、これらのオプションのf(i-1、j-vi)のセットつまり、f(i-1、j-vi)のこれらのオプションの最大値。F(I-1、J-VI)は、i番目の項目の最大値が除去されfは(I-1、J-VI)+ wiは1からiと総体積ずに、i番目のアイテムを取得することができjの最大値を超えないようにしてください
iがある場合とない場合の最大値を取得すると、最大値の合計は2つの最大値になります。したがって、f(i、j)= Max(f(i-1、j-vi)+ wi、f(i-1、j))
ここに画像の説明を挿入します
動的計画法にはテンプレートがありませんが、上記のアイデアがあります。

#include<iostream>
#include<algorithm>
using namespace std;
const int N=1010;
int n,m;        //n件物品,容量为m
int v[N],w[N];  //体积和价值
int f[N][N];    //f表示只从前i种物品中取出体积<=j的最大价值
int main(){
    
    
    cin>>n>>m;
    for(int i=1;i<=n;i++){
    
    
        cin>>v[i];
        cin>>w[i];
    }
    //初始化状态有f[0-n][0-m]
    //从前i种物品中选出体积<=0的最大价值为0
    //从前0种物品中选出体积为任何值的最大价值为0
    //初始化全局变量就全部是0
    
    //求从前i种物品中选出体积<=j的最大价值
    for(int i=1;i<=n;i++){
    
    
        for(int j=1;j<=m;j++){
    
    
            //右边有可能不存在,只有当j>=v[i]的时候才存在,
            if(j>=v[i]){
    
        //如果存在,就考虑选第i种物品和不选第i种物品的最大值
                f[i][j]=f[i-1][j]>f[i-1][j-v[i]]+w[i]?f[i-1][j]:f[i-1][j-v[i]]+w[i];
            }else{
    
      //不存在,就不选
                f[i][j]=f[i-1][j];
            }
        }
    }
    cout<<f[n][m];
    
    return 0;
}

ここで、1次元の状況を考えます。1次元への変換は2つの方向によって決定されます。状態遷移方程式を見てみましょう。f(i、j)を計算するときは、レイヤーi-1のみが使用されます。i-2レイヤーは役に立たないので、ローリングアレイを使用してそれを行うことができます。また、選択されているかどうかに関係なく、j以下であるため、1次元配列を使用して計算できます。

#include<iostream>
#include<algorithm>
using namespace std;
const int N=1010;
int n,m;        //n件物品,容量为m
int v[N],w[N];  //体积和价值
int f[N];    //f表示只从前面物品中取出体积<=j的最大价值
int main(){
    
    
    cin>>n>>m;
    for(int i=1;i<=n;i++){
    
    
        cin>>v[i];
        cin>>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;
}

完全なナップサック問題

問題の説明

N種類のアイテムとV容量のバックパックがあります。各アイテムには無制限のアイテムがあります。

i番目のアイテムのボリュームはviで、値はwiです。

バックパックに詰めるアイテムを見つけて、これらのアイテムの総量がバックパックの容量を超えないようにし、合計値が最大になるようにします。
最大値を出力します。

入力形式
最初の行2つの整数N、Vはスペースで区切られ、それぞれオブジェクトの数とバックパックの体積を示します。

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

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

データ範囲
0 <N、V≤10000
<vi、wi≤1000
入力例
4 5
1 2
2 4
3 4
4 5
出力例:
10

問題解決のアイデア:

問題を解決するという考え方は、基本的に01ナップサックに似ています。同じ方法で
分析します。最初に状態表現を分析します。01ナップサック問題と同様に、状態はf [i、j]であり、状態表現で設定されるとは、すべてのアイテムが最初のiアイテムのみを考慮し、合計ボリュームがj以下のすべてのオプションを意味します。属性:合計値の最大値を示します。
状態計算:セットの分割を示します。01ナップサック問題は、選択するかどうか、つまりi番目の項目に0または1を選択するかどうかの2つのセクションに分かれています。そしてここでは、i番目のアイテムに選択されたアイテムの数に応じて、それを多くのグループに分けることができます。最初のサブセットは0を選択することを意味し、2番目のサブセットは1を選択することを意味し、以下同様にkまで続きます。現在、f [i、j]で示されるすべての選択方法、つまり、最初のi項目のみを考慮し、総量がj以下であるすべての選択方法を、非常に多くのカテゴリに分割できます。 i番目の項目は0のみを選択し、2番目はi番目の項目で、1のみを選択します...各サブセットの値を確認できます。最初のサブセットは、i番目の項目が選択されておらず、最初のiのみが選択されていることを意味します。アイテムが考慮され、i番目のアイテムは選択されません。これは最初のi-1アイテムのみを考慮することに相当し、合計ボリュームはjの最大値を超えません。f[i-1、jを使用できます。 ]それを表す。そして、i番目のアイテムにk個のアイテムを選択するとします。これは、3つのステップに分けて見つけることができます。
1.k個のアイテムを削除しますi2。削除
後の最大値を見つけます。f [i-1]、[jk * v [i]] 3.k
個のアイテム

if [i-1、jk v [i]] + k w [i]を追加し直します。式によると、k = 0の場合、この式も
ここに画像の説明を挿入します
素朴なアプローチを満たしますが、時間計算量は非常に大きく、O(n * v2)であることがわかります。

#include<iostream>
#include<algorithm>
using namespace std;
const int N=1010;
int n,m;            //物品种类数和背包容积
int v[N],w[N];      //vi和wi表示第i种物品的体积和价值
int f[N][N];        //f[i][j]表示只考虑前i种物品且总体积不超过j的最大价值
int main(){
    
    
    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=0;j<=m;j++){
    
          //体积
            //这里k是不能无限大的,j与v[i]乘积必须要小于等于体积j
            for(int k=0;k*v[i]<=j;k++){
    
    
                f[i][j]=max(f[i][j],f[i-1][j-v[i]*k]+w[i]*k);
            }
        }
    }
    cout<<f[n][m]<<endl;
    return 0;
}

それを最適化する方法は?
私たちはそれについて考えることができます:
f [i] [j] = f [i-1] [jv [i] * k] + w [i] * kは
同等です

f[i][j]=max(f[i-1][j],f[i-1][j-v]+w,f[i-1][j-2v]+2w,f[i-1][j-3v]+3w...)
f[i][j-v]=max(        f[i-1,j-v],   f[i-1,j-2v]+w,  f[i-1,j-3v]+2w....)

f [i] [j]とf [i] [jv]は非常に似ていることがわかりますが、各項目が次の項目よりもwだけ多い点が異なります。たとえば、f [i] [j] = f [i-1、jv] + wの場合
、f [i、j] = Max(f [i-1、j]、f [i-1、jv] + w
もう一度実行してみましょう

#include<iostream>
#include<algorithm>
using namespace std;
const int N=1010;
int n,m;            //物品种类数和背包容积
int v[N],w[N];      //vi和wi表示第i种物品的体积和价值
int f[N][N];        //f[i][j]表示只考虑前i种物品且总体积不超过j的最大价值
int main(){
    
    
    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=0;j<=m;j++){
    
          //体积
            f[i][j]=f[i-1][j];
            if(v[i]<=j){
    
    
                f[i][j]=max(f[i][j],f[i][j-v[i]]+w[i]);
            }
        }
    }
    cout<<f[n][m]<<endl;
    return 0;
}

01ナップサック問題と完全なナップサック問題の現在の方程式を比較することができます

f[i,j]=Max(f[i-1,j],f[i-1,j-v]+w)
f[i,j]=Max(f[i-1,j],f[i,j-v]+w)

01バックパック問題はすべてi-1層から転送され、完全なバックパックはi番目の層から転送されていることがわかります。

おすすめ

転載: blog.csdn.net/qq_39736597/article/details/114118381