背包问题
0-1背包
有一堆物品,每个物品都有其重量和价值,现在有一个只能容纳10kg物品的背包,选择装入一些物品,使得背包中的物品价值最大
- DPi,j表示前i个物品,装进容量为j的背包所获得的最大价值
- w[i]为第i件物品的重量
- v[i]为第i件物品的价值
朴素递归
时间复杂度O(2n)
#include<stdio.h>
#include<iostream>
using namespace std;
const int MAXN = 1000 + 10;
int weight[MAXN];
int value[MAXN];
int Fun1(int n, int c){
int answer;
if(n == 0 || c == 0){
answer = 0;
}
else{
if(c < weight[n]){
answer = Fun1(n-1, c);
}
else {
answer = max(Fun1(n - 1, c), Fun1(n - 1, c - weight[n]) + value[n]);
}
}
return answer;
}
int main(){
int c, n;
while(scanf("%d%d", &c, &n) != EOF){
for(int i = 1; i <= n; ++i){
scanf("%d%d", &weight[i], &value[i]);
}
printf("%d\n", Fun1(n, c));
}
return 0;
}
递归策略+记忆化
时间复杂度O(n*c)
#include<stdio.h>
#include<iostream>
using namespace std;
const int MAXN = 1000 + 10;
int weight[MAXN];
int value[MAXN];
int memo[MAXN][MAXN]; //记录,初始化均为-1,表示该子问题未解决
int Fun1(int n, int c){
if(memo[n][c] != -1){ //该子问题已经解决,直接查表
return memo[n][c];
}
int answer;
if(n == 0 || c == 0){
answer = 0;
}
else{
if(c < weight[n]){
answer = Fun1(n-1, c);
}
else {
answer = max(Fun1(n - 1, c), Fun1(n - 1, c - weight[n]) + value[n]);
}
}
memo[n][c] = answer; //记录子问题的解
return answer;
}
int main(){
int c, n;
while(scanf("%d%d", &c, &n) != EOF){
for(int i = 1; i <= n; ++i){
scanf("%d%d", &weight[i], &value[i]);
}
for(int i = 0; i <= n; ++i){
for(int j = 0; j <= c; ++j){
memo[i][j] = -1;
}
}
printf("%d\n", Fun1(n, c));
}
return 0;
}
递推求解
时间复杂度O(n*c)
空间复杂度O(n*c)
#include<stdio.h>
#include<iostream>
using namespace std;
const int MAXN = 1000 + 10;
int weight[MAXN];
int value[MAXN];
int dp[MAXN][MAXN];
int main(){
int c, n;
while(scanf("%d%d", &c, &n) != EOF){
for(int i = 1; i <= n; ++i){
scanf("%d%d", &weight[i], &value[i]);
}
//初始化dp(相当于递归出口)
for(int i = 0; i <= n; ++i){ //背包空间为0
dp[i][0] = 0;
}
for(int j = 0; j <= c; ++j){ //选取物品数为0
dp[0][j] = 0;
}
for(int i = 1; i <= n; ++i){
for(int j = 1; j <= c; ++j){
if(j < weight[i]){
dp[i][j] = dp[i-1][j];
}
else{
dp[i][j] = max(dp[i-1][j], dp[i-1][j-weight[i]] + value[i]);
}
}
}
printf("%d\n", dp[n][c]);
}
return 0;
}
空间优化
由于求解第i个物品是否放入背包时,只需要用到第i-1个物品的状态,因此可以将二位数组压缩为一维数组,只需要记录第i-1件物品的状态,求第i件物品状态的时候反向求解即可(正向求解会提前覆盖需要使用的数据)
时间复杂度O(n*c)
空间复杂度O(c)
#include<stdio.h>
#include<iostream>
using namespace std;
const int MAXC = 1000 + 10;
int weight[MAXC];
int value[MAXC];
int dp[MAXC]; //改为一维数组,记录第i-1件物品的状态
int main(){
int c, n;
while(scanf("%d%d", &c, &n) != EOF){
for(int i = 1; i <= n; ++i){
scanf("%d%d", &weight[i], &value[i]);
}
for(int j = 0; j <= c; ++j){ //初始化
dp[j] = 0;
}
for(int i = 1; i <= n; ++i){
for(int j = c; j >= weight[i]; --j){ //对第i件物品逆向求解
//对于j<weight[i]的情况不用更新
dp[j] = max(dp[j], dp[j-weight[i]] + value[i]);
}
}
printf("%d\n", dp[c]);
}
return 0;
}
完全背包
完全背包和0-1背包(背包中某件物品的数量要么是0要么是1)的区别在于,同一件物品可以有无穷多件,这一点体现在递推公式中为第i件物品放入背包后仍可以继续选择第i件物品
递推求解
#include<stdio.h>
#include<iostream>
using namespace std;
const int MAXN = 100 + 10;
const int MAXM = 1E5 + 10;
int weight[MAXN];
int value[MAXN];
int dp[MAXN][MAXM];
int main(){
int n, m;
while(scanf("%d%d", &n, &m) != EOF){
for(int i = 1; i <= n; ++i){
scanf("%d%d", &value[i], &weight[i]);
}
scanf("%d", &m);
//初始化
for(int i = 0; i <= n; ++i){
dp[i][0] = 0;
}
for(int j = 0; j <= m; ++j){
dp[0][j] = 0;
}
for(int i = 1; i <= n; ++i){
for(int j = 1; j <= m; ++j){
if(j < weight[i]){
dp[i][j] = dp[i-1][j];
}
else{
dp[i][j] = max(dp[i-1][j], dp[i][j-weight[i]] + value[i]);
}
}
}
printf("%d\n", dp[n][m]);
}
return 0;
}
空间优化
时间复杂度O(n*m)
空间复杂度O(n*m)
完全背包在求解第i件物品的状态时只需要用到第i-1件物品的状态和第i件物品的状态,因此可将二维数组压缩为一维数组,只记录第i-1个物品的状态,在求解第i件物品的状态时更新即可
完全背包空间优化和0-1背包空间优化的区别在于,0-1背包在求解第i件物品的状态时是反向更新,要保证第i-1件物品的状态不变。而完全背包在求解第i件物品的状态时,需要更新后的第i-1件物品的状态,因此采用正向更新
#include<stdio.h>
#include<iostream>
using namespace std;
const int MAXN = 100 + 10;
const int MAXM = 1E5 + 10;
int weight[MAXN];
int value[MAXN];
int dp[MAXM]; //改为一维数组,记录第i-1行状态
int main(){
int n, m;
while(scanf("%d%d", &n, &m) != EOF){
for(int i = 1; i <= n; ++i){
scanf("%d%d", &value[i], &weight[i]);
}
scanf("%d", &m);
//初始化
for(int j = 0; j <= m; ++j){
dp[j] = 0;
}
for(int i = 1; i <= n; ++i){
for(int j = weight[i]; j <= m; ++j){ //正向更新
//j < weight[i]时不用更新
dp[j] = max(dp[j], dp[j-weight[i]] + value[i]);
}
}
printf("%d\n", dp[m]);
}
return 0;
}
多重背包
多重背包问题介于0-1背包和完全背包问题之间,即每件物品既不是只有0-1件,也不是有无穷多见,而是有k件,问此时如何选择使得装入背包物品的总价值最大。
思路1:将多重背包问题转为0-1背包问题,即将k件物品看作价值相同且重量相同的k件不同物品
思路2:先组合,将k件相同的物品组合成1件物品,其价值和重量均为原来的k倍,再作为0-1背包问题求解。但不能任意进行组合,组合完成后需要能够满足任意需求的这件物品,则组合策略可以为
举个例子,k=5,按上述组合策略,5=1+2+2,可满足任意需求的物品数
1=1,2=2,3=1+2,4=2+2,5=1+2+2
#include<stdio.h>
#include<iostream>
using namespace std;
const int MAXN = 100 + 10;
const int MAXM = 1000 + 10;
int weight[MAXN];
int value[MAXN];
int amount[MAXN]; //每件物品的数量
int dp[MAXM];
int newWeight[20 * MAXN]; //组合后每件物品的重量
int newValue[20 * MAXN]; //组合后每件物品的价值
int main(){
int n, m;
while(scanf("%d%d", &n, &m) != EOF){
int number = 0; //组合后物品的数量
for(int i = 1; i <= n; ++i){
scanf("%d%d%d", &value[i], &weight[i], &amount[i]);
//按照组合策略进行组合
for(int j = 1; i <= amount[i]; j *= 2){ //组合2^n
++number;
newWeight[i] = j * weight[i];
newValue[i] = j * value[i];
amount[i] -= j;
}
if(amount[i] > 0){ //组合剩余的
++number;
newWeight[i] = amount[i] * weight[i];
newValue[i] = amount[i] * value[i];
}
}
//一下调用0-1背包问题:递推求解+空间优化
//初始化
for(int j = 0; j <= m; ++j){
dp[j] = 0;
}
for(int i = 1; i <= number; ++i){ //number为组合后物品数量
for(int j = m; j >= newWeight[i]; --j){ //newWeight为组合后物品重量
dp[j] = max(dp[j], dp[j-newWeight[i]] + newValue[i]); //newValue为组合后物品价值
}
}
printf("%d\n", dp[m]);
}
return 0;
}