01+完全+多重背包问题

动态规划五个步骤:

  1. 确定dp数组(dp table)以及下标的含义

  2. 确定递推公式

  3. dp数组如何初始化

  4. 确定遍历顺序

  5. 举例推导dp数组

0/1背包

有n种物品,每种物品只有一个,每个物品有自己的重量和自己的价值,有一个最多只能放重量为m的背包,问这个背包最多能装价值为多少的物品

暴力方法:用回溯,每个物品只有两个状态

物品为:

重量价值
物品0115
物品1320
物品2430
动规五部曲:
1)确定dp数组的含义:  dp[i][j],表示[0,i]之间的物品种任取一个,放进容量为j的背包,价值综合最大是多少.
2)递推公式
    不放物品i  dp[i-1][j]                       当前背包容量小于物品的重量
    放物品i    dp[i-1][j-weight[i]]+value[i];   当前背包容量大于等于物品的重量,背包容量减去物品i的容量加上i的价值
    dp[i][j]=max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i])   
3)初始化(当前元素肯定又左上方推导而来,所以第一行和第一列都要先初始化,再考虑其他)
x轴:背包容量
y轴:物品编号
​
                        0   1   2   3   4 
                物品0    0  15  15  15  15
                物品1    0   0  0    0   0
                物品2    0   0  0    0   0
                
    vector<vector<int>> dp(weight.size(), vector<int>(bagweight + 1, 0));
    for (int j = weight[0]; j <= bagweight; j++) {
        dp[0][j] = value[0];//初始化第一行
    }
​
4)确定遍历顺序:先遍历物品或者先遍历背包重量都可以
    先遍历物品:
    for(int i =1,i<weight.size();i++){  //遍历物品
        for(int j =0;j<=bagweight;j++){ // 遍历物品,从物品1开始,跳过物品0
            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]);
​
        }
    }
    先遍历背包容量:
    for(int j = 0; j <= bagweight; j++) { // 遍历背包容量
        for(int i = 1; i < weight.size(); i++) { // 遍历物品,从物品1开始,跳过物品0
            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]);
        }
    }
​
​
x轴:背包容量
y轴:物品编号
​
                        0   1   2   3   4 
                物品0    0  15  15  15  15
                物品1    0  15  15  20  35
                物品2    0  15  15  20  35

二维数组实现

void test_2_wei_bag_problem1() {
    vector<int> weight = {1, 3, 4};
    vector<int> value = {15, 20, 30};
    int bagweight = 4;
​
    // 二维数组
    vector<vector<int>> dp(weight.size(), vector<int>(bagweight + 1, 0));
​
    // 初始化
    for (int j = weight[0]; j <= bagweight; j++) {
        dp[0][j] = value[0];
    }
​
    // weight数组的大小 就是物品个数
    for(int i = 1; i < weight.size(); i++) { // 遍历物品
        for(int j = 0; j <= bagweight; 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]);
        }
    }
​
    cout << dp[weight.size() - 1][bagweight] << endl;
}
​
int main() {
    test_2_wei_bag_problem1();
}

滚动(一维)数组实现:把二维降成一维

dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i])可得,当前层是由上一层的数值推导出来的

倒序遍历是为了保证物品i只被放入一次

两个嵌套for循环的顺序不能颠倒,因为一维dp的写法,背包容量一定是要倒序遍历,如果遍历背包容量放在上一层,那么每个dp[j]就只会放入一个物品,即:背包里只放入了一个物品

//dp数组含义:dp[j]                            表示为容量为j的背包所背的最大价值   例如,dp[0]表示容量为0得背包所能装的最大价值
//递推公式:  dp[j - weight[i]] + value[i]     表示容量为 j - 物品i重量 的背包 加上 物品i的价值。
//初始化:    dp[0]=0;非0下标初始化成最小非负数防止覆盖后面的值 ,因此统一初始化为0 
//遍历顺序:  必须先遍历物品再遍历背包容量,且背包容量必须倒序(保证每个物品只被添加一次)
顺序:
dp[1]=dp[1-1]+15=15;
dp[2]=dp[2-1]+15=30;
倒序:
dp[2]=dp[2-1]+15;
dp[1]=dp[1-1+15];
void test_1_wei_bag_problem() {
    vector<int> weight = {1, 3, 4};
    vector<int> value = {15, 20, 30};
    int bagWeight = 4;
​
    // 初始化
    vector<int> dp(bagWeight + 1, 0);
    for(int i = 0; i < weight.size(); i++) { // 遍历物品
        for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量
            dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
        }
    }
    cout << dp[bagWeight] << endl;
}
​
int main() {
    test_1_wei_bag_problem();
}

完全背包

有n种物品,每种物品有无限个

在完全背包中,对于一维dp数组来说,其实两个for循环嵌套顺序是无所谓的,因为dp[j] 是根据 下标j之前所对应的dp[j]计算出来的。 只要保证下标j之前的dp[j]都是经过计算的就可以了。

//重后往前,可以重复
// 先遍历物品,在遍历背包
void test_CompletePack() {
    vector<int> weight = {1, 3, 4};
    vector<int> value = {15, 20, 30};
    int bagWeight = 4;
    vector<int> dp(bagWeight + 1, 0);
    for(int i = 0; i < weight.size(); i++) { 
        for(int j = weight[i]; j <= bagWeight; j++) { // 与01背包不同点,从小到大可以保证物品被添加多次
            dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
        }
    }
    cout << dp[bagWeight] << endl;
}
int main() {
    test_CompletePack();
}

多重背包

有n种物品,每种物品的个数各不相同

重量价值数量
物品01152
物品13203
物品24302

展开(就是01背包):

重量价值数量
物品01151
物品01151
物品13201
物品13201
物品13201
物品24301
物品24301
#include<iostream>
#include<vector>
using namespace std;
int main() {
    int bagWeight,n;
    cin >> bagWeight >> n;
    vector<int> weight(n, 0); 
    vector<int> value(n, 0);
    vector<int> nums(n, 0);
    for (int i = 0; i < n; i++) cin >> weight[i];
    for (int i = 0; i < n; i++) cin >> value[i];
    for (int i = 0; i < n; i++) cin >> nums[i];    
    
    for (int i = 0; i < n; i++) {
        while (nums[i] > 1) { // 物品数量不是一的,都展开
            weight.push_back(weight[i]);
            value.push_back(value[i]);
            nums[i]--;
        }
    }
 
    vector<int> dp(bagWeight + 1, 0);
    for(int i = 0; i < weight.size(); i++) { // 遍历物品,注意此时的物品数量不是n
        for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量
            dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
        }
    }
    cout << dp[bagWeight] << endl;
}

优化:

#include<iostream>
#include<vector>
using namespace std;
int main() {
    int bagWeight,n;
    cin >> bagWeight >> n;
    vector<int> weight(n, 0);
    vector<int> value(n, 0);
    vector<int> nums(n, 0);
    for (int i = 0; i < n; i++) cin >> weight[i];
    for (int i = 0; i < n; i++) cin >> value[i];
    for (int i = 0; i < n; i++) cin >> nums[i];
​
    vector<int> dp(bagWeight + 1, 0);//优化后
​
    for(int i = 0; i < n; i++) { // 遍历物品
        for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量
            // 以上为01背包,然后加一个遍历个数
            for (int k = 1; k <= nums[i] && (j - k * weight[i]) >= 0; k++) { // 遍历个数
                dp[j] = max(dp[j], dp[j - k * weight[i]] + k * value[i]);
            }
        }
    }
​
    cout << dp[bagWeight] << endl;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值