0-1揹包
重要部分:每次每個物品最多隻能選擇一次。
題面:有n個重量和價值分別爲 Wi 與 Vi 的物品,現在從這些物品中挑選出總重量不超過 s 的物品,並且要求總價值最大。
輸入:
n=4
S=5
(w,v) = {(2,3)(1,2)(3,4)(2,2)}
輸出:
7(選擇0,1,3號物品)
對於這個問題我們可以先用最樸素的方法,判斷每個物品是否放入揹包中。
#include<bits/stdc++.h>
using namespace std;
#define MAX_N 20000 //宏定義
//輸入
int n,s;
int w[MAX_N],v[MAX_N];
//從第 i 個物品開始挑選總重小於 j 的部分
int rec(int i,int j){
int res;
if(i==n){
res=0;
}else if(j<w[i]){
res=rec(i+1,j);
}else{
res=max(rec(i+1,j-w[i]+v[i]),rec(i+1,j));
}
return res;
}
int main(){
scanf("%d%d",&n,&s);
for(int i=0;i<n;i++){
scanf("%d%d",&w[i],&v[i]);
}
printf("%d\n",rec(0,s));
return 0;
}
而使用這個方法的話時間複雜度比較大,當n比較大的時候就會時間超限,所以我們需要優化算法,上面的那個方法對於重複出現的又計算了一遍所以造成時間長,我們可以把每次計算結果都存起來下次再計算到該部分的時候直接拿來使用減少計算時間。
優化:
int dp[MAX-N][MAX_N];
memset(dp,-1,sizeof(dp));//初始化
int rec(int i,int j){
if(dp[]i[j]>0){
return dp[i][j];
}
int res;
if(i==n){
res=0;
}else if(j<w[i]){
res=rec(i+1,j);
}else{
res=max(rec(i+1,j-w[i]+v[i]),rec(i+1,j));
}
return dp[i][j]=res;
}
我們上面用的是遞歸的方法,我們也可以用遞推的方法來階矩這個問題。遞推解決這個問題有兩個方向,一個是倒序一個是正序。
倒序:
int dp[MAX-N][MAX_N];
void dp_ditui(){
for(int i=n-1;i>=0;i--){
for(int j=0;j<=S;j++){
if(j<w[i]){
dp[i][j]=dp[i+1][j];
}else{
dp[i][j]=max(dp[i+1][j],dp[i+1][j-w[i]]+v[i]);//遞推式子不同
}
}
}
printf("%d\n",dp[0][s]);//輸出不同
正序:
void dp_ditui(){
for(int i=0;i<n;i++){
for(int j=0;j<=S;j++){
if(j<w[i]){
dp[i+1][j]=dp[i][j];
}else{
dp[i+1][j]=max(dp[i][j],dp[i][j-w[i]]+v[i]);
}
}
}
printf("%d\n",dp[n][s]);//輸出不同
以上就是0-1揹包的幾種常見解決方法,最常用的是後兩種。
//正序與倒序的區別:
除了方向相反以外還有以下區別:
//正序
printf("%d\n",dp[n][s]);
//倒序
printf("%d\n",dp[0][s]);
完全揹包
重要部分: 每個物品可以選擇的次數不止一次,每個物品都可以選擇多次。
題面同上
#include<stdio.h>
#include<bits/stdc++.h>
using namespace std;
#define MAX_N 10000
int dp[MAX_N][MAX_N],n,w[MAX_N],v[MAX_N],S;
void dp_ditui(){
for(int i=0;i<n;i++){
for(int j=0;j<=S;j++){
if(j<w[i]){
dp[i+1][j]=dp[i][j];
}else{
dp[i+1][j]=max(dp[i][j],dp[i+1][j-w[i]]+v[i]); //注意
}
}
}
printf("%d\n",dp[n][S]);
}
int main(){
scanf("%d%d",&n,&S);
for(int i=0;i<n;i++){
scanf("%d%d",&w[i],&v[i]);
}
dp_ditui();
return 0;
}
從上面的代碼可以看出完全揹包的代碼與0-1揹包的代碼似乎是完全一樣的,但是仔細看的話可以發現區別在這裏:
//完全揹包
dp[i+1][j]=max(dp[i][j],dp[i+1][j-w[i]]+v[i]); //注意
//0-1揹包
dp[i+1][j]=max(dp[i][j],dp[i][j-w[i]]+v[i]);
另外無論是0-1揹包還是完全揹包我們也都可以用一個數組實現。
0-1揹包:
int dp[MAX_N];
void solve(){
for(int i=0;i<n;i++){
for(int j=s;j>=w[i];j--){
dp[j]=max(dp[j],dp[j-w[i]+v[i]);
}
}
printf("%d\n",dp[s]);
}
完全揹包:
int dp[MAX_N];
void solve(){
for(int i=0;i<n;i++){
for(int j=w[i];j<=s];j++){
dp[j]=max(dp[j],dp[j-w[i]+v[i]);
}
}
printf("%d\n",dp[s]);
}
二者的區別只在與 j 的循環方向:
0-1揹包:
for(int j=s;j>=w[i];j--)
完全揹包:
for(int j=w[i];j<=s;j++)