単純な01ナップサック問題
#include <iostream>
#define read(x,y) scanf("%d%d",&x,&y)
using namespace std;
const int maxn=1010,maxv=1010;
int v[maxn],w[maxn]; //v体积,w价值
int dp[maxv];
int main()
{
int N,V;
read(N,V);
for (int i=1;i<=N;i++) read(v[i],w[i]);
for (int i=1;i<=N;i++)
for (int j=V;j>=v[i];j--)"更新当前物品在所有背包体积下的最优选择,不能选择的就不更新了,直接继承"
dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
printf("%d",dp[V]);
return 0;
}
タイトル説明
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つの状態があり、このアイテムを選択できないようにする必要がありますが、このアイテムを選択できるかどうかは、現在のバックパックのサイズによって異なります。
- オブジェクトのボリュームが現在のバックパックのボリューム以下の場合、選択済みと未選択の2つの状態があります。
- オブジェクトのボリュームが現在のバックパックのボリュームよりも大きい場合、唯一のオプションはこの状態を選択しないことです。
2次元配列の実装
#include <iostream>
#define read(x,y) scanf("%d%d",&x,&y)
using namespace std;
const int maxn=1010,maxv=1010; //最多1000个物品,价值最大为1000
int v[maxn],w[maxn]; //记录每个物品的体积v还有价值w,从下标1开始存物品,方便和dp数组对应
int dp[maxn][maxv]; //选择前N个物品,体积最大为V
int main()
{
int N,V;
read(N,V);
for (int i=1;i<=N;i++) read(v[i],w[i]); "从下标1开始存储物品,和dp数组对应"
"开始递推过程,递推方程:dp[i][j]=max(dp[i][j],dp[i-1][j-v[i]]+w[i]);"
for (int i=1;i<=N;i++) //先循环物品,再循环体积,先行后列。
for (int j=1;j<=V;j++) {
//从0开始最好,0~V
dp[i][j]=dp[i-1][j]; "不选第i个物品是必然成立的,如果他不能选择第i个物品就直接继承上一行"
if (j>=v[i]) dp[i][j]=max(dp[i][j],dp[i-1][j-v[i]]+w[i]);
} "在可以选择第i个物品的条件下,判断是否要选择第i个物品"
printf("%d",dp[N][V]);
return 0;
}
dp [i] [j]は、最初のi個のアイテムを前提としたさまざまなオプションの最大値を表し、バックパックの重量はj
です。アイテムをバックパックに入れることができる場合、動的転送方程式はdp[i][j]=max( dp[i-1][j],dp[i-1][ j-v[i] ]+ w[i] )
、j> =です。 w [i]
アイテムをバックパックdp[i][j]=dp[i-1][j]
に入れることができない場合、j <w [i]
ただし、各アイテムはバックパックに入れないように選択する必要があるため、上記のコードでの記述を選択することもできます。
再帰コア:最初のiアイテムの最適解を選択するときは、最初のi-1アイテムの最適解に基づいてi番目のアイテムを選択するかどうかを決定するだけで済みます。容量の異なるバックパックがそれであるかどうかi番目のアイテムを選択することは同じではありません。
最終結果:2次元dpテーブルは、すべての場合にアイテム選択の最大ボリュームを記録します。最初のNアイテムが選択され、バックパックボリュームがVの場合にのみ、最終的な最大値が必要です。dp[N][V]
最初にアイテムをリサイクルし、次にボリュームをリサイクルしますか?最初にボリュームをリサイクルしてから、アイテムをリサイクルします
この動的伝達方程式を観察します。i番目の行では、前の行のデータ、つまりi-1番目の行のみが必要です。j番目の列では、j番目の列とその左側の列のデータのみに依存します。つまり、最初のj列です。その更新dp[i,j]
各のみから、I-1 2次元テーブルDP最初の行を使用する私たちの、その後の最適化のための基礎を提供するデータ、および私たちがしなければならないトラバーサル問題の順番を考えます:dp[i-1,0]
dp[i-1][j]
- 上から下へ、最初の行、次に列をトラバースします。つまり、最初にアイテムを循環させ、次にボリュームを循環させます。各アイテムの下にV容量のバックパックは、最良の選択を決定し、行ごとに更新する必要があります。下の各行は、もう1つのアイテムを表します。
- 左から右にトラバースし、最初に列、次に行、つまり、ボリュームが最初に循環され、次にアイテムが循環されます。各ボリュームの下にN個の異なるバックパックがあります。最初の行を歩くときは最初のアイテムを選択し、2番目の行を歩くときは最初の2つのアイテムを選択します。最初の行のデータは最初の行まで必要です... 。N行の場合、V列が常にループするように、N- 1行のデータが必要です。
ただし、2つのトラバーサルモードでの初期化の問題に注意してください。
- 上から下にトラバースします、シークするとき
dp[i][j]
は、i-1行目のすべてのデータのみを使用します。配列が境界を越えないようにするため、0行目から処理を開始することはできません。1行目から開始する必要があるため、0行目は最初に0に初期化されます。つまり、さまざまなサイズのアイテムの最大値である0を選択しません。次に、アイテムが1から列挙され、バックパックのボリュームが0から列挙されます。
for (int i=1;i<=N;i++)
for (int j=0;j<=V;j++)
バックパックのボリュームが0からVに列挙された理由は、これがv[i]==j
時間を防ぐためである場合、jv [i] = 0、状況は列0に戻り、列0は実際には各行を維持する必要があります。 jが0の場合dp[i][0]=dp[i-1][0]
、(ボリュームが0のアイテムがない限り)のみであるため、dp[i][0]
最終的dp[0][0]
には行0で初期化された0と等しくなるため、初期化中に0番目の列を0に直接初期化できます。およびバックパック上記のコードに示すように、ボリュームは1から直接列挙できます。
for (int i=1;i<=N;i++)
for (int j=1;j<=V;j++)
- 左から右にトラバースしますを求めて
dp[i][j]
、配列の境界(-1)が最初から列挙されたボリュームをバックパックしなければならないのを防ぐために、新しく更新された行データを含む最初のデータ行i-1の前の左側のj番目の列を使用できます。1から列挙し、左から右、上から下に列挙する必要があります。次の行は前の行のデータを使用する必要があります。したがって、初期化するときは、0行目と0列目を0に初期化する必要があります。dp[i-1][j]
for (int j=1;j<=V;j++)
for (int i=1;i<=N;i++)
行0の意味を初期化します:行0は何も表さないので、バックパックのサイズに関係なく、値は0です。したがって
dp[0][j]=0;
、最初の列の意味を初期化します:どのアイテムを選択しても、バックパックのサイズは制限されます0に設定するため、の値も0である必要があります。などdp[i][0]=0;
ローリングアレイの実装
最初にアイテムをリサイクルし、次にバックパックボリュームをリサイクルする方法を使用します。これは、彼が毎回前の行のデータのみを使用し、次に2行V列の2次元配列を定義するためです。int dp[2][V];
そうであれば最初のバックパックボリュームをリサイクルし、次にアイテムをリサイクルする際にDPを算出し、[I-1] [j]のDPを算出した後、[i] [j]は、彼は最初のJ上のi番目の行を使用することができますcolumnデータの場合、見つかったばかりのdp [i-1] [j]を使用する必要があります。これには、維持するために3つ以上の列が必要であるため、ローリングアレイで最適化できるのは最初のループメソッドのみです。
- XOR演算によって変数pを定義できます。p= 0の場合はp ^ 1 = 1、
p = 1の場合はp ^ 1 = 0なので、0行目と1行目を繰り返し切り替えることができます。- 介してAND。操作、アイテムiの行の数を表すことができ、およびI&1が最下位ビットを取り出すことができ、それが偶数である場合に横断、iが0であり、それが奇数である場合、iが1であるとします0行目か1行目かを判断します。
#include <iostream>
#define read(x,y) scanf("%d%d",&x,&y)
using namespace std;
const int maxn=1010,maxv=1010;
int v[maxn],w[maxn]; //v体积,w价值
int dp[2][maxv];
int main()
{
int N,V;
read(N,V);
for (int i=1;i<=N;i++) read(v[i],w[i]);
//dp递推,预处理:0行0列初始化为0,体积可以从1处理
int p=0; "每次循环结束时,p指向处理完后的那一行,上次处理的是第0行。"
for (int i=1;i<=N;i++) {
for (int j=1;j<=V;j++) {
//p^1表示当前处理的行
if (j>=v[i]) dp[p^1][j]=max(dp[p][j],dp[p][j-v[i]]+w[i]); //可以选择物品i,在是否选择之间抉择
else dp[p^1][j]=dp[p][j]; //不能选择物品[i],直接继承上一行
}
p^=1; "p=p^1; 在在第0行和第1行之间来回切换"
}
printf("%d",dp[p][V]);
return 0;
}
1次元配列の実装
ローリング配列の最適化は、i番目の行の計算を通じてi-1番目の行のデータのみを使用する性質であるため、より具体的に説明します。
更新dp[i,j]
時には、dp2のi-1番目の行のみ-次元テーブルは、たびに使用される。からdp[i-1,0]
のdp[i-1][j]
データ。
したがって、1次元配列として最適化することができ、バックパックボリュームjが小さいほど使用回数が多くなり、次のレイヤーのバックパックボリュームjとVの間で使用されることがわかります。したがって、forループの2番目のレベルは逆の順序になります。dp[i] [j]を探すときは、dp [i-1] [V]がdp [i]から除外されないため、最初にj = Vから始めます。 [V] Dp [i] [j]が使用され、dp [i-1] [V]はこの更新後は役に立たないため、dp [i] [V]はdp [i-1] [V]を上書きできます。降順で更新します。
終了を決定するための条件は、バックパック容量jがバックパックにロードされるアイテムのボリュームv [i]より大きいかどうかです。これは、jがv [i]より小さい場合、前述のことから直接わかるためです。 dp [i] [j] = dp [i -1] [j]は、前の行のデータを継承します。1次元配列を使用しているため、直接継承できます。
#include <iostream>
#define read(x,y) scanf("%d%d",&x,&y)
using namespace std;
const int maxn=1010,maxv=1010;
int v[maxn],w[maxn]; //v体积,w价值
int dp[maxv];
int main()
{
int N,V;
read(N,V);
for (int i=1;i<=N;i++) read(v[i],w[i]);
for (int i=1;i<=N;i++)
for (int j=V;j>=v[i];j--)"更新当前物品在所有背包体积下的最优选择,不能选择的就不更新了,直接继承"
dp[j]=max(dp[j],dp[j-v[i]]+w[i]); //进入for循环的都是可以选择物品i的情况
printf("%d",dp[V]);
return 0;
}