古典的な01ナップサック問題は最大値を解きます。この問題は特定の値Mに正確に等しい必要があります。変換する方法は?
M以下の条件での最大値は解くことができ、M以下の場合は問題は解決せず、M以下の場合は理解が得られます。したがって、この質問では、重量と価値は通貨の単位です。
dp配列の計算とパスのバックトラッキングには、固定の書き込み方法があります。この質問の難しさは、バックトラッキングから派生したパスシーケンスが最小になるようにする方法です。
最小のシーケンスの定義は、このように表現できます。つまり、先頭の数値の値が小さいほど、シーケンスは小さくなります。
パスのバックトラックは「後ろ」の番号から始まり、「前」に戻ります。
したがって、このような戦略を考えると、通貨を大きいものから小さいものに並べて、バックトラックが小額の通貨から始まるようにすることができます。ここでの重要なロジックは、通貨が特定の時点で選択されているかどうかにかかわらず、dp値です。が等しい場合は選択します。選択しないわけではありません。この方法でのみ、シーケンスを最小化できます。
コード:
#include <cstdio>
#include <algorithm>
int N, M;
bool cmp(int a, int b);
int main(){
scanf("%d%d", &N, &M);
int coin[N+1];
int len=0;
for(int i=0; i<N; i++){
int a;
scanf("%d", &a);
if(a<=M) coin[++len] = a;
}
std::sort(coin+1, coin+len+1, cmp);
int dp[len+1][M+1]={
};
for(int i=1; i<=len; i++){
for(int x=1; x<=M; x++){
if(x>=coin[i]) dp[i][x] = std::max(dp[i-1][x], dp[i-1][x-coin[i]]+coin[i]);
else dp[i][x] = dp[i-1][x];
}
}
if(dp[len][M]!=M) printf("No Solution");
else{
bool selected[len+1]={
};
int i=len, x=M, sc=0;
while(i>0 && x>0){
if(dp[i-1][x]==dp[i-1][x-coin[i]]+coin[i] || dp[i][x]!=dp[i-1][x]){
//从后往前回溯,若选与不选dp相等,优先选,这样能保证序列最小
selected[i] = true;
sc++;
//i--;
//x -= coin[i];
x -= coin[i];
i--;
}
else{
i--;
}
}
for(int j=len; j>=1; j--){
if(!selected[j]) continue;
printf("%d", coin[j]);
sc--;
if(sc>0) printf(" ");
}
}
return 0;
}
bool cmp(int a, int b){
return a>b;
}