1.01背包
问题描述
n个物品,每个物品的重量为wi和pi,且每种物品只有一个,现有一个背包,容量为W,往背包里面放物品,要是放的物品价值最大。
题解
对于每种物品来说,只有放与不放两种可能。dp[i][j] 表示当容量是j的时候,第i个物品放或者不放的情况下背包的最大价值。同时我们会发现dp[i]的数组只和dp[i-1]有关,所以,我们可以对普通的01背包进行优化,将dp优化成一维数组。
代码:
#include<iostream>
#include<cstring>
using namespace std;
const int N = 100;
int n , W;
int w , p;
int dp[N];
int main(){
cin >> n >> W;
memset(dp , 0 , sizeof(dp));
for(int i = 1; i <= n;i++){
cin >> w >> p;
for(int j = W;j >= w;j--){
dp[j] = max(dp[j] , dp[j - w] + p);//关键的动态规划表达式
}
}
cout << dp[W] << endl;
return 0;
}
2.完全背包
问题描述
n个物品,每个物品的重量为wi和pi,每种物品有若干个,现有一个背包,容量为W,往背包里面放物品,要是放的物品价值最大。
题解
如果说01背包是物品放和不放的问题,那个完全背包就是物品放不放和放几个的问题。所以最简单的思路就是再套一层循环,判断放0个、1个……哪种最好。
代码
#include<iostream>
using namespace std;
const int N = 100;
int n , W;
int w, p;
int dp[N][10000];
int main(){
cin >> W >> n;
for(int i = 1;i <= n;i++){
cin >> w >> p;
for(int j = 1;j <= W;j++){
int num = j / w;
for(int t = 0;t <= num;t++){
dp[i][j] = max(dp[i - 1][j] , dp[i][j - t * w] + t * p);
}
}
}
cout << dp[n][W] << endl;
return 0;
}
优化
首先和01背包一样,可以对dp数组降维,将dp降成一维数组。其次,我们可以将第三重循环去掉,因为就是往背包里面放0个,1个,…..,一直到W/w[i]个,取其最优解。
最优代码
#include<iostream>
using namespace std;
const int N = 100;
int n , W;
int w, p;
int dp[10000];
int main(){
cin >> W >> n;
for(int i = 1;i <= n;i++){
cin >> w >> p;
for(int j = w;j <= W;j++){
dp[j] = max(dp[j] , dp[j - w] + p); //关键的动态规划表达式,和01背包的代码进行对比
}
}
cout << dp[W] << endl;
return 0;
}
3.多重背包
问题描述
n个物品,每个物品的重量为wi和pi,每种物品有ti个,现有一个背包,容量为W,往背包里面放物品,要是放的物品价值最大。
题解
如果说01背包是物品放和不放的问题,那个完全背包就是物品放不放和放几个的问题。那么当多重背包中的ti ≤ W / wi 时,可以看作一个01背包;当多重背包中的ti > W / wi 时,可以看作一个完全背包。
代码:
void ZeroOnePack(int w,int p){
for(int j = v;j >= w;j--){
if(dp[j - w]+p > dp[j])
dp[j] = dp[j - w]+p;
}
}
void CompletePack(int w,int p){
for(int j = w;j <= v;j++){
if(dp[j - w]+p > dp[j])
dp[j] = dp[j - w]+p;
}
}
void MultiPack(int w,int p,int num){
if(num > v/w){
//CompletePack
CompletePack(w , p);
}else{
//ZeroOnePack
int k = 1;
while(k < num){
ZeroOnePack(k*w,k*p);
num -= k;
k <<= 1;
}
ZeroOnePack(num * w , num * p );
}
}
例题以及AC代码
悼念512汶川大地震遇难同胞——珍惜现在,感恩生活
根据t的数量拆分成0-1背包和完全背包两种背包问题求解。
#include<iostream>
#include<cstring>
using namespace std;
int w , p , t ,num;
int dp[400000 + 10];
int n,v;
void ZeroOnePack(int w,int p){
for(int j = v;j >= w;j--){
if(dp[j - w]+p > dp[j])
dp[j] = dp[j - w]+p;
}
}
void CompletePack(int w,int p){
for(int j = w;j <= v;j++){
if(dp[j - w]+p > dp[j])
dp[j] = dp[j - w]+p;
}
}
void MultiPack(int w,int p,int num){
if(num > v/w){
//CompletePack
CompletePack(w , p);
}else{
//ZeroOnePack
int k = 1;
while(k < num){
ZeroOnePack(k*w,k*p);
num -= k;
k <<= 1;
}
ZeroOnePack(num * w , num * p );
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int exm;
cin >> exm;
while(exm--){
memset(dp , 0 , sizeof(dp));
cin >> v >> n;
for(int i = 1;i <= n;i++){
cin >> w >> p >> t;
MultiPack(w , p , t);
}
cout << dp[v] << endl;
}
return 0;
}