複数のナップサック問題
タイトル説明
N個のアイテムと容量Vのバックパックがあります。
I番目のアイテムせいぜいsiアイテムがあります、各ピースのボリュームはviで、値はwiです。
アイテムの総量がバックパックの容量を超えないように、どのアイテムがバックパックにロードされているかを解決し、合計値が最大になります。最大値を出力します。
入力形式
最初の行の2つの整数N、Vはスペースで区切られ、オブジェクトの数とバックパックの体積を示します。
次に、N行があり、それぞれに3つの整数vi、wi、siがあり、スペースで区切られ、i番目のアイテムの量、値、および数量を示します。
出力形式
最大値を表す整数を出力します。
データ範囲
ケース1: 0 <N、V≤100、0 <vi、wi、si≤100
ケース2: 0 <N≤1000、0 <V≤2000、0 <vi、wi、si≤2000
入力サンプル
4 5
1 23体積値数量
24 1
3 4 3
4 5 2
サンプル出力:
10
分析:
完全なバックパックの各アイテムは無制限に使用できるため、制限は1つだけk*v<=V
です。、選択肢の数kは
バックパックの容量によって制限されます。複数のバックパックにはアイテムが含まれるため、2つの制限があります。k<=s && k*v<=V
kはアイテム自体の数によって制限されます。
動的伝達方程式は次の条件を満たす:、dp[i][j]=max{dp[i-1][ j-k×v[i] ] + k×w[i]}
ここで0 ≤ k×v[i] ≤ j && k<=s
ときはk<=s
時間しかしためのk*v<=V
時間と終了、十分にその記事自体、完全なナップザック問題の同等示す、
しかしときのでk>s
終了し、それが複数のナップザック問題、である、項目数が小さすぎであることを示します。
ここで、複数のナップザックの問題について考えます。つまり、各アイテムの数sは比較的少なく、すべてのアイテムをバックパックにパックできます。つまりs*v<=V
、kはアイテムの数sによってのみ制限されます。
アイテム数が十分に多い場合は、sに到達k*v<=V
する前に存在するため、途中で終了する場合があります。
2次元配列の実装
#include <iostream>
#define read(x) scanf("%d",&x)
#define rep(i,a,b) for (int i=a;i<=b;i++)
using namespace std;
const int maxn=110,maxv=110;
int v[maxn],w[maxn],s[maxn];
int dp[maxn][maxv];
int main() {
int N,V;
read(N),read(V);
rep(i,1,N) read(v[i]),read(w[i]),read(s[i]);
rep(i,1,N)
rep(j,1,V)
for (int k=0;k<=s[i] && k*v[i]<=j;k++)
dp[i][j]=max(dp[i][j],dp[i-1][j-k*v[i]]+k*w[i]);
printf("%d",dp[N][V]);
return 0;
}
ただし、このアプローチはより時間的に複雑です。時間計算量O(NVS)、Case1のデータのみ使用可能で、Case2のデータはTLEになります。完全なバックパックと同じように最適化すれば、アイテム数が十分であれば、完全なバックパックであり、最適化できます。でもアイテム数が少ないときsが比較的少ないときは?
Jの最大値にそれをロードし、i番目のアイテムの体積をVとすると、値をWであり、そしてsは項目がある:
展開f[i,j]=MAX { f[i−1,j−k∗v]+k∗w }
で、k = 0,1、...、次のようです。
上記の拡張式の前提は次のとおりです。
(s+1)*v<=j
つまりs*v<=j-v
、説明:ボリュームjのバックパックはこのアイテムのs + 1個を保持できます。つまり、ボリュームjvのバックパックはこのアイテムのs個を保持できます。
この場合、完全なバックパックのように最適化することはできません。完全なバックパックdp[i][j]=max(dp[i-1][j],dp[i][j-v]+w)
内のアイテムの種類に関係なく、そのような結果を最適化でき、この方法で最適化できる完全なバックパック内のアイテムがあります。また、この方法で最適化できないアイテム。、すべてが満たされるわけではありません。
バイナリ最適化
コア:体積がv、値がw、数がsのアイテムをk個のアイテムに逆アセンブルします。これらのk個のアイテムは完全に同等であり、違いはなく、順序の問題もありません。
2進数を考えてみましょう:11111111、10進数255を表す8つの数字。これらの8つの位置から0または1を取ることにより、0から255までのすべての数字を記述することができます。
これらの81は、1、2、4、8、16、32、64、128を表します。つまり、これらの8つの数字のいずれかを選択することにより、0から255までのすべての数字を組み合わせ、すべてを選択できます。すべてを選択するのではなく、0です。
したがって、10進数Nの場合、1、2、4、...、2 (k-1)、N-(2 k -1)に分割できます。注:これは、次のk +1個の数値に分割されます。
それら0とNの間の任意の組み合わせは、0とNの間のすべての数を形成できます。
最初のk個の数値は0から2k-1 -1までの任意のデータを構成でき、さらにN-(2 k -1)は0からNまでの任意のデータを構成できます。
s = 200と仮定すると、それを分解することができ200=1+2+4+8+16+32+64+73
、これらの値は重みと比例係数です。
次に、ボリュームv、値w、および数値sのこのアイテムを8つのアイテムに分解できます。
v [1] = v、w [1] = w
v [2] = 2v、w [2] = 2w
v [3] = 4v、w [3] = 4w
v [4] = 8v、w [4] = 8w
v [5] = 16v、w [5] = 16w
v [6] = 32v、w [6] = 32w
v [7] = 64v、w [7] = 64w
v [8] = 73v、w [ 8] = 73w
新しく分割された8つのアイテムはオプションであるかどうかにかかわらず、各アイテムはこのように分割されるため、01バックパック問題になります。
このように、sアイテムの場合、ログに分割して1つのアイテム全体を取得でき、時間計算量はO(NVS)からO(NVlogS)の範囲です。
アルゴリズムの実装
Case2のデータ範囲は次のとおりです。0<N≤1000、0 <V≤2000、0 <vi、wi、si≤2000
元々は最大1000個のアイテムがあり、各アイテムの最大ボリュームは2000です。ボリュームは2000で、最大でlog(2000)+1に分割できるため、実際のアイテム数は2つの積になります。
viの最大値は2000、log2000はlog(2×1000)= log2 + log1000 = 1 + 3×
log10、log10は3より大きく4より小さいため、log2000は10より大きく13未満です。
#include <iostream>
#define read(x) scanf("%d",&x)
using namespace std;
const int maxn=1010,maxv=2010;
int v[maxn*14],w[maxn*14];
int dp[maxv];
int main()
{
int N,V;
read(N),read(V);
//边输入边预处理,将每个物品进行拆分
int a,b,s;
int idx=0; "idx指向数组的真实的最后一个位置"
for (int i=1;i<=N;i++) {
read(a),read(b),read(s);
//进行拆分
int k=1;
while (k<=s) {
v[++idx]=k*a,w[idx]=k*b;//拆分装入背包
s-=k,k*=2;
}
if (s) v[++idx]=s*a,w[idx]=s*b;
//不是2^k的话,最后还会剩下一个数
}
N=idx;//拆分后共有idx个物品
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;
}
最初に分割せずに処理することも可能です。配列を追加メンテナンスせずに分割して処理できます。ナップサック問題-混合ナップサック
のコストは、値w配列の数値が役に立たず、意味がないことです。入力の再処理中に破棄され、最後の分割の結果が記録されます。
単調キューの最適化
アップグレード待ち。