背包问题

背包问题汇总 详细解析参考“背包九讲”:https://github.com/tianyicui/pack 目录 1. 背包问题 I ——0-1背包无价值 2. 背包问题II ——0-1背包有价值 3. 背包问题III —— 完全背包问题 小结1 4. 背包问题IV /V ——求方案数 5. 背包问题VII —— 多重背包问题 6. 多重背包可行性解 7. 换零钱问题 1. 背... 阅读详情

这篇博客记录在leetcode刷题中遇到的一些背包问题。

目录

完全背包问题

1.518. 零钱兑换 II

2 322. 零钱兑换

3. 377. 组合总和 Ⅳ

4. 1449. 数位成本和为目标值的最大数字

01背包问题

1. 494. 目标和 

2. 879. 盈利计划

3. 416. 分割等和子集

4.1049. 最后一块石头的重量 II

5. 474. 一和零

多重背包

1. 宝物筛选

2. POJ 1014 Dividing

 3. 1363. 形成三的最大倍数

混合背包

1.P1833 樱花

分组背包

1. P1757 通天之分组背包

有依赖的背包

1.P1064 金明的预算方案


完全背包问题

1.518. 零钱兑换 II

518. 零钱兑换 II 给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。 

示例 1:

输入: amount = 5, coins = [1, 2, 5]
输出: 4
解释: 有四种方式可以凑成总金额:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1

//思路:

这一题是典型的完全背包问题,即:在一组数中选出一些数,和为给定值,每个数字可以取无限个,求不同的组合(不同的取法)数。

对于背包问题,一般用二维dp,这里定义dp[i][j]的含义是,在[0, i-1]下标的数中,选一些数,可以组成和为j的组合数。

那么,对 0 <= i <= coins.size(),dp[i][0] = 1,意为无论有多少个数可以选择(包括没有数可以选的情况),1个数都不选,只有这一种取法能使和为0。

接下来考虑转移方程。dp[i][j]表示现在有[0, i-1]范围的数字可以选,对于下标为 i-1的数,可以选,也可以不选。如果不选,那么取法是dp[i-1][j]。如果选了下标是[i-1]的数,由于是完全背包,仍然可以继续选[i-1],所以取法是 dp[i][j - coins[i]],这里要保证 j >= coins[i]。由于这里的i, j是从左到右,从上到下计算的,因此 dp[i][j - coins[j]]的结果已经在 dp[i][j]之前计算出来了。

因此,转移方程即为 dp[i][j] = \left\{\begin{matrix} 1, j = 0& &\\dp[i-1][j], j < coins[i]]\\dp[i-1][j] + dp[i][j - coins[i]], j \geq coins[i] & \end{matrix}\right.

class Solution {
public:
    int change(int amount, vector<int>& coins) {
        vector<vector<int>> dp(coins.size() + 1, vector<int>(amount + 1, 0));
        for (int i = 0; i <= coins.size(); ++i)
        {
            dp[i][0] = 1;
        }
        for (int i = 1; i <= coins.size(); ++i)
        {
            for (int j = 1; j <= amount; ++j)
            {
                dp[i][j] = dp[i-1][j];
                if(j >= coins[i-1]) dp[i][j] += dp[i][j - coins[i-1]];
            }
        }
        return dp[coins.size()][amount];
    }
};

 

 由于 dp[i][j]的值只与 dp[i-1][j] 和 dp[i][j - coins[i-1]]有关,因此可以把dp数组优化成一维的,并且 j 从左向右更新。对照转移方程dp[i][j] = \left\{\begin{matrix} 1, j = 0& &\\dp[i-1][j], j < coins[i]]\\dp[i-1][j] + dp[i][j - coins[i]], j \geq coins[i] & \end{matrix}\right. 

空间优化成1维dp后,由于dp[i][j]取决于dp[i][j-coins[i]],所以j的遍历依然是从左向右,依次更新dp[i][j],而未更新的部分是dp[i-1][j]。。

参考这一篇题解https://leetcode-cn.com/problems/coin-change-2/solution/ling-qian-dui-huan-iihe-pa-lou-ti-wen-ti-dao-di-yo/

class Solution {
public:
    int change(int amount, vector<int>& coins) {
        int n = coins.size();
        vector<int> dp(amount + 1, 0);
        dp[0] = 1;
        for (int i = 0; i < n; ++i)
        {
            for (int j = coins[i]; j <= amount; ++j)
            {
                dp[j] += dp[j-coins[i]];
            }
        }
        return dp[amount];
    }
};

相同的问题还有https://leetcode-cn.com/problems/coin-lcci/ 面试题 08.11. 硬币 

322. 零钱兑换

给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。

 

示例 1:

输入: coins = [1, 2, 5], amount = 11
输出: 3 
解释: 11 = 5 + 5 + 1
示例 2:

输入: coins = [2], amount = 3
输出: -1
 

说明:
你可以认为每种硬币的数量是无限的。

和上一题类似,不过这里要求的是最少需要的硬币个数。同样的,定义 dp[i][j]: 使用coins[0...i-1]来组合成j,最少需要的硬币个数。初始化 dp[i][0] = 0, for 0 <= i <= coins.size(),意为组合成0只需要0个硬币。之后,考虑 dp[i][j],如果不使用 coins[i],那么最少需要 dp[i-1][j];如果使用 coins[i],那么需要 dp[i][j-coins[i]] 个硬币,二者取较小的那个。即递推公式:

dp[i][j] = \left\{\begin{matrix} 0, j = 0& &\\dp[i-1][j], j < coins[i]]\\min(dp[i-1][j], dp[i][j - coins[i]] + 1), j \geq coins[i] & \end{matrix}\right.

当然这里需要注意的是可能不能组成需要的值,因此初始化的时候除了j=0的情况,其它值都应该初始化为 INT32_MAX/2,/2是为了避免在j >= coins[i]时因为要计算 +1操作发生溢出。

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        int n = coins.size();
        vector<vector<int>> dp(n+1, vector<int>(amount+1, INT32_MAX/2));
        for (int i = 0; i <= n; ++i)
        {
            dp[i][0] = 0;
        }
        for (int i = 1; i <= n; ++i)
        {
            for (int j = 1; j <= amount; ++j)
            {
                dp[i][j] = dp[i-1][j];
                if(j >= coins[i-1]) dp[i][j] = min(dp[i][j], dp[i][j-coins[i-1]] + 1);
            }
        }
        return dp[n][amount] < INT32_MAX/2 ? dp[n][amount] : -1;
    }
};

同样的,因为求dp[i] 只涉及到 dp[i-1],可以压缩成一维dp,j的遍历顺序是 coins[i] -> amount

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        int n = coins.size();
        vector<int> dp(amount + 1, INT32_MAX/2);
        dp[0] = 0;
        for (int i = 0; i < n; ++i)
        {
            for (int j = coins[i]; j <= amount; ++j)
            {
                dp[j] = min(dp[j], dp[j-coins[i]] + 1);
            }
        }
        return dp[amount] < INT32_MAX/2 ? dp[amount] : -1;
    }
};

 

3. 377. 组合总和 Ⅳ

377. 组合总和 Ⅳ 给定一个由正整数组成且不存在重复数字的数组,找出和为给定目标正整数的组合的个数。

示例:

nums = [1, 2, 3]
target = 4

所有可能的组合为:
(1, 1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(1, 3)
(2, 1, 1)
(2, 2)
(3, 1)

请注意,顺序不同的序列被视作不同的组合。

因此输出为 7。

//思路:

这一题看上去与完全背包问题很像,都是选出一些数,和为给定值,并且每个数可以无限选。但是不同之处在于,它要求的是排列数,因此,不能用完全背包去做,因为背包问题的结果是顺序无关的。

定义 dp[j] 是从所有数中选出和为 j的数的排列总数。需要考虑 dp[j] 和 dp[x] (x <= j)的关系。

对于 dp[j],这个 j 可以由 ? + nums[i]组成,而在最后的结果中,每一个 nums[i] 放在末尾,它能构成的排列总数就是 dp[j - nums[i]]。因此,转移方程就是

dp[j] = dp[j - nums[0]] + dp[j - nums[1]] + ....dp[j - nums[k]]

k\in \{j >= nums[k]\}

定义dp[0] = 1,语义是,nums[k] == j 时,dp[j - nums[k]] = 1,单独成为一种组合。

如果不好理解的话,可以类比爬楼梯问题:

https://leetcode-cn.com/problems/climbing-stairs/

70. 爬楼梯 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

注意:给定 n 是一个正整数。

示例 1:

输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。
1.  1 阶 + 1 阶
2.  2 阶
示例 2:

输入: 3
输出: 3
解释: 有三种方法可以爬到楼顶。
1.  1 阶 + 1 阶 + 1 阶
2.  1 阶 + 2 阶
3.  2 阶 + 1 阶

对于这一问题,状态方程 dp[j] = dp[j-1] + dp[j-2]是怎么得到的呢?我们要爬 j 级台阶,可以先爬 j-1的台阶,最后爬1个台阶;也可以先爬 j-2 的台阶,最后爬2个台阶。因此,dp[j] = dp[j-1] + dp[j-2],可以看到方法是考虑顺序的。

对于这个问题,相当于我们一次能爬nums[0]...nums[k]级台阶,问要爬target级台阶,有多少种方法,当然是分为最后爬 nums[0]级、最后爬 nums[1]级、......最后爬 nums[k]级这些情况,并把它们相加。

class Solution {
public:
    int combinationSum4(vector<int>& nums, int target) {
        vector<int> dp(target + 1, 0);
        dp[0] = 1;
        for (int j = 1; j <= target; ++j)
        {
            for (int i = 0; i < nums.size(); ++i)
            {
                if(j >= nums[i]) dp[j] += dp[j - nums[i]];
            }
        }
        return dp[target];
    }
};

这个问题更详细的说明参考这篇题解:https://leetcode-cn.com/problems/combination-sum-iv/solution/dong-tai-gui-hua-python-dai-ma-by-liweiwei1419/

4. 1449. 数位成本和为目标值的最大数字

给你一个整数数组 cost 和一个整数 target 。请你返回满足如下规则可以得到的 最大 整数:

给当前结果添加一个数位(i + 1)的成本为 cost[i] (cost 数组下标从 0 开始)。
总成本必须恰好等于 target 。
添加的数位中没有数字 0 。
由于答案可能会很大,请你以字符串形式返回。

如果按照上述要求无法得到任何整数,请你返回 "0" 。

 

示例 1:

输入:cost = [4,3,2,5,6,7,2,5,5], target = 9
输出:"7772"
解释:添加数位 '7' 的成本为 2 ,添加数位 '2' 的成本为 3 。所以 "7772" 的代价为 2*3+ 3*1 = 9 。 "997" 也是满足要求的数字,但 "7772" 是较大的数字。
 数字     成本
  1  ->   4
  2  ->   3
  3  ->   2
  4  ->   5
  5  ->   6
  6  ->   7
  7  ->   2
  8  ->   5
  9  ->   5
示例 2:

输入:cost = [7,6,5,5,5,6,8,7,8], target = 12
输出:"85"
解释:添加数位 '8' 的成本是 7 ,添加数位 '5' 的成本是 5 。"85" 的成本为 7 + 5 = 12 。
示例 3:

输入:cost = [2,4,6,2,4,6,4,4,4], target = 5
输出:"0"
解释:总成本是 target 的条件下,无法生成任何整数。
示例 4:

输入:cost = [6,10,15,40,40,40,40,40,40], target = 47
输出:"32211"
 

提示:

cost.length == 9
1 <= cost[i] <= 5000
1 <= target <= 5000

本质是完全背包问题,即无限制地选一些数,它们的总和满足一定条件。但是与普通完全背包问题有两个不一样的地方:

(1) 要求选出的数的cost总和刚好等于target,而不是 <= target。

(2) 要求恢复选择的结果。

因为要使得组成的数字最大,显然长度要最长,所以dp数组应该记录最大长度的信息。定义 dp[i][j]: 从数字[1...i]里选,能够组成j的最大长度。

状态转移方程:

dp[i][j] = \left\{\begin{matrix} 0, i= 0, j = 0& &\\dp[i-1][j], j < cost[i-1]\\max(dp[i-1][j],dp[i][j-cost[i-1]]+1), j \geq cost[i-1] & \end{matrix}\right.

 对于第一点不同,就要在dp数组初始化的时候小心,初始化dp[0][0] = 0, 其它dp[i][j] 都初始化为接近INT32_MIN的数,这样才能使得在不能刚好组成j的时候,dp[i][j] 依然是个负数,也就是无效结果。

对于第二点不同,在长度最长的时候,应当从最大的数字开始循环找可行的解,也就是从最大的数字 ei = 9 和 最后的总和 ej = target 开始,循环判断 dp[ei][ej] == dp[ei][ej - cost[ei-1]] + 1,如果是,那么说明ei被选择,否则,ei--。

class Solution {
public:
    string largestNumber(vector<int>& cost, int target) {
        int dp[10][5001];
        //dp[i][j]: 从[1...i]里选,能组成代价和为j的最大长度
        memset(dp, 0x80, sizeof(dp));
        for(int i = 0; i <= 9; i++) dp[i][0] = 0;
        for(int i = 1; i <= 9; i++)
        {
            for(int j = 1; j <= target; j++)
            {
                dp[i][j] = dp[i-1][j];
                if(j >= cost[i-1]) dp[i][j] = max(dp[i][j], dp[i][j-cost[i-1]] + 1);
            }
        }
        if(dp[9][target] <= 0) return "0";
        int ei = 9, ej = target;
        string ans;
        while(dp[ei][ej] > 0)
        {
            if(ej >= cost[ei-1] && dp[ei][ej] == dp[ei][ej-cost[ei-1]] + 1)
            {
                ans += ('0' + ei);
                ej -= cost[ei-1];
            }
            else ei--;
        }
        return ans;
    }
};

优化到一维同样可以恢复选择的信息,只需要每一次循环中从最大的数开始寻找可行解。

class Solution {
public:
    string largestNumber(vector<int>& cost, int target) {
        const int MAXT = 5000;
        int dp[MAXT+1];
        memset(dp, 0x80, sizeof(dp));
        dp[0] = 0;
        for(int i = 1; i <= 9; i++)
        {
            for(int j = cost[i-1]; j <= target; j++)
            {
                dp[j] = max(dp[j], dp[j-cost[i-1]] + 1);
            }
        }
        if(dp[target] <= 0) return "0";
        string ans;
        int ej = target;
        while(ej > 0)
        {
            for(int i = 9; i >= 1; i--)
            {
                if(ej >= cost[i-1] && dp[ej] == dp[ej-cost[i-1]] + 1)
                {
                    ans += ('0' + i);
                    ej -= cost[i-1];
                    break;
                }
            }
        }
        return ans;
    }
};

 

01背包问题

1. 494. 目标和 

给定一个非负整数数组,a1, a2, ..., an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。

返回可以使最终数组和为目标数 S 的所有添加符号的方法数。

示例 1:

输入: nums: [1, 1, 1, 1, 1], S: 3
输出: 5
解释: 

-1+1+1+1+1 = 3
+1-1+1+1+1 = 3
+1+1-1+1+1 = 3
+1+1+1-1+1 = 3
+1+1+1+1-1 = 3

一共有5种方法让最终目标和为3。

//思路

解决本题的重要突破点在于想到将+ - 两部分分开考虑,即选择一部分数取它们的和a,剩下的数取它们的相反数的和b,那么有

a+b = S, a-b = sum(nums)。从而,可以得到 a = (S + sum(nums)) / 2,问题也就转化成01背包问题:选择一些数,它们的和是a,每个数只能选一次,问有多少种取法。

注意这里 nums[i] 是非负整数,所以注意 dp[i][j] 的 j 遍历时从0开始, dp[0][0] 初始化为1,dp[i][0](i >= 1)不能确定,因为可能有一些 nums[i-1]本身就是0。

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int S) {
        long long sum = 0;
        for(int i : nums) sum += i;
        if(sum < S || (sum + S) % 2 == 1) return 0;
        sum = (sum + S) / 2;
        vector<vector<int>> dp(nums.size() + 1, vector<int>(sum + 1, 0));
        dp[0][0] = 1;
        for (int i = 1; i <= nums.size(); ++i)
        {
            for (int j = 0; j <= sum; ++j)
            {
                dp[i][j] = dp[i-1][j];
                if(j >= nums[i-1]) dp[i][j] += dp[i-1][j-nums[i-1]];
            }
        }
        return dp.back()[sum];
    }
};

很容易看到,代码几乎与完全背包的(A)一模一样,分为两种情况,不取nums[i-1],和取nums[i-1]。但是由于是0-1背包,每个数只能取一次,所以选了第i-1个数之后,只能在[0, i-2]中找凑出 j - nums[i-1]的方案数目,所以取法是dp[i-1][j-nums[i]]。转移方程如下:

dp[i][j] = \left\{\begin{matrix} 1, i = 0, j = 0& \\dp[i-1][j], j <nums[i] \\dp[i-1][j] + dp[i-1][j - nums[i]], j \geq nums[i] & \end{matrix}\right.

01背包的空间优化:一维dp注意与完全背包的一维dp的不同之处,j 的遍历方向不同。这是因为01背包的dp[i][j]取决于上一行的dp[i-1][j-nums[i-1]],所以j要从右向左进行更新,更新的部分是dp[i][j],未更新的部分是dp[i-1][j],从右向左避免覆盖。而完全背包的dp[i][j]取决于dp[i][j-nums[i-1]],所以j要从左向右更新。

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int S) {
        long long sum = 0;
        for(int i : nums) sum += i;
        if(sum < S || (sum + S) % 2 == 1) return 0;
        sum = (sum + S) / 2;
        vector<int> dp(sum + 1, 0);
        dp[0] = 1;
        for(int i : nums)
        {
            for (int j = sum; j >= i; --j)
            {
                dp[j] += dp[j - i];
            }
        }
        return dp[sum];
    }
};

2. 879. 盈利计划

帮派里有 G 名成员,他们可能犯下各种各样的罪行。

第 i 种犯罪会产生 profit[i] 的利润,它要求 group[i] 名成员共同参与。

让我们把这些犯罪的任何子集称为盈利计划,该计划至少产生 P 的利润。

有多少种方案可以选择?因为答案很大,所以返回它模 10^9 + 7 的值。

 

示例 1:

输入:G = 5, P = 3, group = [2,2], profit = [2,3]
输出:2
解释: 
至少产生 3 的利润,该帮派可以犯下罪 0 和罪 1 ,或仅犯下罪 1 。
总的来说,有两种方案。
示例 2:

输入:G = 10, P = 5, group = [2,3,5], profit = [6,7,8]
输出:7
解释:
至少产生 5 的利润,只要他们犯其中一种罪就行,所以该帮派可以犯下任何罪行 。
有 7 种可能的计划:(0),(1),(2),(0,1),(0,2),(1,2),以及 (0,1,2) 。
 

提示:

1 <= G <= 100
0 <= P <= 100
1 <= group[i] <= 100
0 <= profit[i] <= 100
1 <= group.length = profit.length <= 100

也是01背包计数问题,要注意的是这里要求的是利润大于K的数量,所以在二维背包问题上又增加了一维。但是,因为所有可组合的利润最大能达到100*100,如果定义dp数组是 方案*容量*利润的话,规模太大。因为题目只要求利润大于等于P的,而P的数量级是100,所以应该定义dp数组 方案*容量*P,把所有利润大于等于P的组合都算到P里。 

定义 dp[i][j][k]: 在[0...i-1]中选,背包容量是j,利润是k的组合数,注意所有大于k的组合也划到等于k里。

初始化要注意所有的k=0都有1种方案,就是都不选。

转移方程:

dp[i][j][k] = \left\{\begin{matrix} 1, k = 0& \\dp[i-1][j][k], j <group[i-1] \\dp[i-1][j][k] + dp[i-1][j - group[i-1]][max(0, k-profit[i-1])], j \geq group[i-1] & \end{matrix}\right.

class Solution {
public:
    int profitableSchemes(int G, int P, vector<int>& group, vector<int>& profit) {
        int base = 1e9 + 7;
        int n = group.size();
        long long dp[101][101][101] = {0};
        //dp[i][j][k]: 从 [0...i-1]里选,有j个人,利润是k的方案数
        for(int i = 0; i <= n; i++)
        {
            for(int j = 0; j <= G; j++)
            {
                dp[i][j][0] = 1;
            }
        }
        for(int i = 1; i <= n; i++)
        {
            for(int j = 1; j <= G; j++)
            {
                for(int k = 0; k <= P; k++)
                {
                    dp[i][j][k] = dp[i-1][j][k];
                    if(j >= group[i-1])
                    {
                        dp[i][j][k] += dp[i-1][j-group[i]][max(0, k-profit[i-1])];
                        dp[i][j][k] %= base;
                    }
                }
            }
        }
        return dp[n][G][P];
    }
};

3. 416. 分割等和子集

给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

注意:

每个数组中的元素不会超过 100
数组的大小不会超过 200
示例 1:

输入: [1, 5, 11, 5]

输出: true

解释: 数组可以分割成 [1, 5, 5] 和 [11].
 

示例 2:

输入: [1, 2, 3, 5]

输出: false

解释: 数组不能分割成两个元素和相等的子集.

两种做法。

(1) 定义 dp[i][j]: 使用 nums[0...i-1], 能否组成j 。那么转移方程是:

 dp[i][j] = \left\{\begin{matrix} true, i = 0, j = 0& \\dp[i-1][j], j <nums[i] \\dp[i-1][j] \: || \: dp[i-1][j - nums[i]], j \geq nums[i] & \end{matrix}\right.

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int sum = accumulate(nums.begin(), nums.end(), 0);
        if(sum & 1) return false;
        sum /= 2;
        int n = nums.size();
        vector<vector<bool>> dp(n+1, vector<bool>(sum+1, false));
        //dp[i][j]: 使用nums[0...i-1]中的数,能否组成j
        dp[0][0] = true;
        for (int i = 1; i <= n; ++i)
        {
            for (int j = 0; j <= sum; ++j)
            {
                dp[i][j] = dp[i-1][j];
                if(j >= nums[i-1]) dp[i][j] = dp[i][j] || dp[i-1][j - nums[i-1]];
            }
        }
        return dp[n][sum];
    }
};

优化成一维:

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int sum = accumulate(nums.begin(), nums.end(), 0);
        if(sum & 1) return false;
        sum /= 2;
        vector<bool> dp(sum+1, false);
        dp[0] = true;
        for(int i : nums)
        {
            for(int j = sum; j >= i; j--)
            {
                dp[j] = dp[j] || dp[j - i];
            }
        }
        return dp[sum];
    }
};

(2) 定义dp[i][j]:使用 nums[0...i-1],能够组成的最大不超过 j 的数,最后只需要判断 dp[n][sum/2] == sum/2 即可。

转移方程是:

dp[i][j] = \left\{\begin{matrix} 0, i = 0, j = 0& \\dp[i-1][j], j <nums[i] \\max(dp[i][j], dp[i-1][j - nums[i]] + nums[i]), j \geq nums[i] & \end{matrix}\right.

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int sum = accumulate(nums.begin(), nums.end(), 0);
        if(sum & 1) return false;
        sum /= 2;
        int n = nums.size();
        vector<vector<int>> dp(n+1, vector<int>(sum+1, 0));
        //dp[i][j]: 使用nums[0...i-1]中的数,能组成最大不超过 sum 的数
        for (int i = 1; i <= n; ++i)
        {
            for (int j = 1; j <= sum; ++j)
            {
                dp[i][j] = dp[i-1][j];
                if(j >= nums[i-1]) dp[i][j] = max(dp[i][j], dp[i-1][j-nums[i-1]] + nums[i-1]);
            }
        }
        return dp[n][sum] == sum;
    }
};

优化成一维:

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int sum = accumulate(nums.begin(), nums.end(), 0);
        if(sum & 1) return false;
        sum /= 2;
        vector<int> dp(sum+1, 0);
        for(int i : nums)
        {
            for(int j = sum; j >= i; j--)
            {
                dp[j] = max(dp[j], dp[j-i] + i);
            }
        }
        return dp[sum] == sum;
    }
};

4.1049. 最后一块石头的重量 II

有一堆石头,每块石头的重量都是正整数。

每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:

如果 x == y,那么两块石头都会被完全粉碎;
如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。
最后,最多只会剩下一块石头。返回此石头最小的可能重量。如果没有石头剩下,就返回 0。

 

示例:

输入:[2,7,4,1,8,1]
输出:1
解释:
组合 2 和 4,得到 2,所以数组转化为 [2,7,1,8,1],
组合 7 和 8,得到 1,所以数组转化为 [2,1,1,1],
组合 2 和 1,得到 1,所以数组转化为 [1,1,1],
组合 1 和 1,得到 0,所以数组转化为 [1],这就是最优值。
 

提示:

1 <= stones.length <= 30
1 <= stones[i] <= 1000

本质是说,分成两组,并且使两组的差尽量小。那么两组的和都应该越接近sum/2越好。也就转化成,选一些数,它们的和不超过sum/2,能得到的最大和,是01背包问题。

class Solution {
public:
    int lastStoneWeightII(vector<int>& stones) {
        int n = stones.size(), sum = accumulate(stones.begin(), stones.end(), 0);
        vector<int> dp(sum/2+1, 0);
        for (int i = 0; i < n; ++i)
        {
            for (int j = sum/2; j >= stones[i]; --j)
            {
                dp[j] = max(dp[j], dp[j-stones[i]] + stones[i]);
            }
        }
        return abs(dp[sum/2] * 2 - sum);
    }
};

 

5. 474. 一和零

在计算机界中,我们总是追求用有限的资源获取最大的收益。

现在,假设你分别支配着 m 个 0 和 n 个 1。另外,还有一个仅包含 0 和 1 字符串的数组。

你的任务是使用给定的 m 个 0 和 n 个 1 ,找到能拼出存在于数组中的字符串的最大数量。每个 0 和 1 至多被使用一次。

注意:

给定 0 和 1 的数量都不会超过 100。
给定字符串数组的长度不会超过 600。
示例 1:

输入: Array = {"10", "0001", "111001", "1", "0"}, m = 5, n = 3
输出: 4

解释: 总共 4 个字符串可以通过 5 个 0 和 3 个 1 拼出,即 "10","0001","1","0" 。
示例 2:

输入: Array = {"10", "0", "1"}, m = 1, n = 1
输出: 2

解释: 你可以拼出 "10",但之后就没有剩余数字了。更好的选择是拼出 "0" 和 "1" 。

相当于两个维度上的01背包问题,所以不进行空间优化的话dp数组应该是三维的。

(1) 定义 dp[i][j][k] : 考察strs[0...i-1],使用 j 个0 和 k 个 1 能组成的最大字符串数。转移方程:

 dp[i][j][k] = \left\{\begin{matrix} i = 0, j = 0, k = 0& \\dp[i-1][j][k], j <zerocnt[i] \: ||\: k<onecnt[i]\\max(dp[i][j][k], dp[i-1][j - zerocnt[i]][k-onecnt[i]] + 1), j \geq zerocnt[i] \: \&\&\: k \geq onecnt[i]& \end{matrix}\right.

class Solution {
public:
    int findMaxForm(vector<string>& strs, int m, int n) {
        int size = strs.size();
        int dp[size+1][m+1][n+1];
        memset(dp, 0, sizeof(dp));
        for (int i = 1; i <= size; ++i)
        {
            int zerocnt = 0, onecnt = 0;
            for(char c : strs[i-1])
            {
                if(c == '0') zerocnt++;
                else onecnt++;
            }
            for (int j = 0; j <= m; ++j)
            {
                for (int k = 0; k <= n; ++k)
                {
                    dp[i][j][k] = dp[i-1][j][k];
                    if(j >= zerocnt && k >= onecnt) dp[i][j][k] = max(dp[i][j][k], dp[i-1][j-zerocnt][k-onecnt] + 1);
                }
            }
        }
        return dp[size][m][n];
    }
};

(2)同样的,dp[i][j][k]只和i-1有关,优化一维:

class Solution {
public:
    int findMaxForm(vector<string>& strs, int m, int n) {
        int dp[101][101];
        memset(dp, 0, sizeof(dp));
        for(const auto& i : strs)
        {
            int onecnt = 0, zerocnt = 0;
            for(char c : i) 
            {
                if(c == '0') zerocnt++;
                else onecnt++;
            }
            for (int j = m; j >= zerocnt; --j)
            {
                for (int k = n; k >= onecnt; --k)
                {
                    dp[j][k] = max(dp[j][k], dp[j-zerocnt][k-onecnt] + 1);
                }
            }
        }
        return dp[m][n];
    }
};

二维费用背包问题还有:https://www.luogu.com.cn/problem/P1855#submit P1855 榨取kkksc03 

多重背包

1. 宝物筛选

小 F 有一个最大载重为 W的采集车,洞穴里总共有 n 种宝物,每种宝物的价值为 vi​,重量为wi​,每种宝物有 mi​ 件。小 F 希望在采集车不超载的前提下,选择一些宝物装进采集车,使得它们的价值和最大。

输入格式

第一行为一个整数 n 和 W,分别表示宝物种数和采集车的最大载重。

接下来 n 行每行三个整数 vi​,wi​,mi​。

输出格式

输出仅一个整数,表示在采集车不超载的情况下收集的宝物的最大价值。

数据规模:n <= 100, W <= 1e4, \sum m_i \leq 1e5

背包容量W,每个物品价值v, 重量w,  个数k,需要找到使得背包价值最大的物品组合。

最简单的做法是,把k个物品展开,本质上还是01背包问题。当然不需要真的展开,k个物品可以选1个,选2个...选k个,直接在循环里处理。

转移方程:dp[i][j]表示从[0...i-1]物品里选,重量不超过j的最大价值

dp[i][j] = \max_{k=0}^{k_i}(dp[i-1][j], k*v[i] + dp[i-1][j-k*w[i]]), j \geq k*w[i]

#include <bits/stdc++.h>
using namespace std;

const int MAXW = 4e4 + 10;
const int MAXN = 110;

struct Node
{
    int v;
    int w;
    int k;
};

int main()
{
    int n, w;
    scanf("%d%d", &n, &w);
    Node all[MAXN];
    for(int i = 0; i < n; i++)
    {
        scanf("%d%d%d", &all[i].v, &all[i].w, &all[i].k);
    }
    int dp[MAXN][MAXW] = {0};
    for(int i = 1; i <= n; i++)
    {
        for(int j = 0; j <= w; j++)
        {
            dp[i][j] = dp[i-1][j];
            for(int k = 1; k <= all[i-1].k; k++)
            {
                int totalw = k * all[i-1].w;
                if(j < totalw) break;
                dp[i][j] = max(dp[i][j], dp[i-1][j-totalw] + k * all[i-1].v);
            }
        }
    }
    printf("%d\n", dp[n][w]);
    return 0;
}

优化到一维:

dp[j] = \max_{k=0}^{k_i}(dp[j], k*v[i] + dp[j-k*w[i]]), j \geq k*w[i]

#include <bits/stdc++.h>
using namespace std;

const int MAXW = 4e4 + 10;
const int MAXN = 110;

struct Node
{
    int v;
    int w;
    int k;
};

int main()
{
    int n, w;
    scanf("%d%d", &n, &w);
    Node all[MAXN];
    for(int i = 0; i < n; i++)
    {
        scanf("%d%d%d", &all[i].v, &all[i].w, &all[i].k);
    }
    int dp[MAXW] = {0};
    for(int i = 0; i < n; i++)
    {
        for(int j = w; j >= all[i].w; j--)
        {
            for(int k = 1; k <= all[i].k; k++)
            {
                int totalw = k * all[i].w;
                if(j < totalw) break;
                dp[j] = max(dp[j], dp[j-k*all[i].w] + k*all[i].v);
            }
        }
    }
    printf("%d\n", dp[w]);
    return 0;
}

转化为01背包的时间复杂度是O(W*\sum k_i),因为本质上还是对所有物品考虑选或者不选。

\sum k_i比较大的时候,会超时。因为做了大量重复计算,例如,考虑了选择第1个物品的第1、2件,和考虑选择第1个物品的第2、3件,这两种选法是等效的,但是因为转换成01背包的时候把它们看成了不同的物品,也就是第i个物品贡献了ki个分组,每个分组都是1,导致了重复计算。

所以,需要减少可供选择的物品种类,才能降低复杂度。

假设第i件物品有ki个,那么它贡献的分组应该能组成<=ki的任意数,但是分组数要尽量少。这就是二进制优化的原理。例如ki = 6,它应该只贡献 1 2 3 三个分组,同时这三个分组一定可以组合出任意 <= 6 的可能。更一般的,第i个物品应该只贡献2^j, 0 <= j <= log_2(k_i+1)-1这些分组,如果ki+1不是2的幂,那么最后还剩下不能表示成2的幂的分组,要单独作为一组,保证由这些分组一定能组成 <= ki的任意数。

那么二进制分组的代码如下,在设定分组的数组大小时要注意分成的组数可能会比n大,但是一定不会超过\sum k_i

    //all:存放二进制分组的结果,最多是每个物品一组,一共有SUMK个物品,会分成SUMK组
    Node all[MAXSUMK];
    //count:最后一共分了多少组
    int count = 0;
    for(int i = 0; i < n; i++)
    {
        int vi, wi, ki;
        scanf("%d%d%d", &vi, &wi, &ki);
        //二进制分组
        int c = 1;
        while(ki >= c)
        {
            all[count++] = {vi*c, wi*c};
            ki -= c;
            c *= 2;
        }
        if(ki > 0) all[count++] = {vi*ki, wi*ki};
    }

得到了所有的二进制分组之后,就可以像01背包一样对每个分组选或不选了。

#include <bits/stdc++.h>
using namespace std;

const int MAXW = 4e4 + 10;
const int MAXSUMK = 1e5 + 10;

struct Node
{
    int v;
    int w;
};

int main()
{
    int n, w;
    scanf("%d%d", &n, &w);
    //all:存放二进制分组的结果,最多是每个物品一组,一共有MAXSUMK个物品,会分成MAXSUMK组
    Node all[MAXSUMK];
    //count:最后一共分了多少组
    int count = 0;
    for(int i = 0; i < n; i++)
    {
        int vi, wi, ki;
        scanf("%d%d%d", &vi, &wi, &ki);
        //二进制分组
        int c = 1;
        while(ki >= c)
        {
            all[count++] = {vi*c, wi*c};
            ki -= c;
            c *= 2;
        }
        if(ki > 0) all[count++] = {vi*ki, wi*ki};
    }
    int dp[MAXW] = {0};
    for(int i = 0; i < count; i++)
    {
        for(int j = w; j >= all[i].w; j--)
        {
            dp[j] = max(dp[j], dp[j-all[i].w] + all[i].v);
        }
    }
    printf("%d\n", dp[w]);
    return 0;
}

复杂度降低到了O(W*log_2\sum k_i)

2. POJ 1014 Dividing

POJ 1014也是一道多重背包问题:大意如下:

有6种物品,第i种物品(i >= 1)的重量是i,有ki个,问能否把这些物品划分为重量相等的两堆。

和01背包中划分等和子集问题很像,不过这里需要用二进制分组来转化成01背包问题。

#include <string>
#include <iostream>
#include <vector>
#include <cstdio>
using namespace std;

const int MAXSUMK = 20000;

int main()
{
    string str;
    int index = 1;
    while(getline(cin, str))
    {
        int nums[7];
        sscanf(str.c_str(), "%d%d%d%d%d%d", &nums[1], &nums[2], &nums[3], &nums[4], &nums[5], &nums[6]);
        int sum = nums[1] + nums[2] * 2 + nums[3] * 3 + nums[4] * 4 + nums[5] * 5 + nums[6] * 6;
        if(sum == 0) break;
        printf("Collection #%d:\n", index++);
        if(sum & 1)
        {
            printf("Can't be divided.\n\n");
            continue;
        }
        sum /= 2;
        vector<bool> dp(sum+1, false);
        dp[0] = true;
        //二进制分组
        int all[MAXSUMK];
        int count = 0;
        for(int i = 1; i <= 6; i++)
        {
            int c = 1;
            while(nums[i] >= c)
            {
                nums[i] -= c;
                all[count++] = c * i;
                c *= 2;
            }
            if(nums[i] > 0) all[count++] = nums[i] * i;
        }
        for(int i = 0; i < count; i++)
        {
            for(int j = sum; j >= all[i]; j--)
            {
                dp[j] = dp[j] || dp[j-all[i]];
            }
        }
        if(dp[sum]) printf("Can be divided.\n\n");
        else printf("Can't be divided.\n\n");
    }
    return 0;
}

 3. 1363. 形成三的最大倍数

给你一个整数数组 digits,你可以通过按任意顺序连接其中某些数字来形成 3 的倍数,请你返回所能得到的最大的 3 的倍数。

由于答案可能不在整数数据类型范围内,请以字符串形式返回答案。

如果无法得到答案,请返回一个空字符串。

 

示例 1:

输入:digits = [8,1,9]
输出:"981"
示例 2:

输入:digits = [8,6,7,1,0]
输出:"8760"
示例 3:

输入:digits = [1]
输出:""
示例 4:

输入:digits = [0,0,0,0,0,0]
输出:"0"
 

提示:

1 <= digits.length <= 10^4
0 <= digits[i] <= 9
返回的结果不应包含不必要的前导零。

可以用多重背包的思路去做,也就是找一些分组,它们的和能被3整除(且大于0),并且长度最大。最后从dp数组里恢复选择的分组信息。时间复杂度比较高,因为最大和(背包容量)可以达到90000。看评论可以用贪心去做,待补。

class Solution {
public:
    string largestMultipleOfThree(vector<int>& digits) {
        int h[10] = {0};
        int maxsum = 0;
        for(int i : digits)
        {
            h[i]++;
            maxsum += i;
        }
        //数位最多的,并且和是3的倍数的分组
        vector<pair<int,int>> all;
        //{和,个数}
        for(int i = 0; i <= 9; i++)
        {
            int k = h[i], c = 1;
            while(k >= c)
            {
                k -= c;
                all.push_back({c*i, c});
                c *= 2;
            }
            if(k > 0) all.push_back({k*i, k});
        }
        int n = all.size();
        vector<vector<int>> dp(n+1, vector<int>(maxsum + 1, INT32_MIN / 2));
        dp[0][0] = 0;
        //dp[i][j]: 从all[0...i-1]里选,能组成j的分组的最大长度
        for(int i = 1; i <= n; i++)
        {
            for(int j = 0; j <= maxsum; j++)
            {
                dp[i][j] = dp[i-1][j];
                if(j >= all[i-1].first) dp[i][j] = max(dp[i][j], dp[i-1][j-all[i-1].first] + all[i-1].second);
            }
        }
        int ei = n, ej = -1, maxlen = 0;
        for(int j = 1; j <= maxsum; j++)
        {
            if(j % 3 == 0)
            {
                if(dp[n][j] >= maxlen)
                {
                    maxlen = dp[n][j];
                    ej = j;
                }
            }
        }
        if(ej < 0)
        {
            if(dp[n][0] > 0) return "0";
            else return "";
        }
        vector<pair<int,int>> ans;
        //{数,个数}
        while(ei > 0 && ej >= 0)
        {
            if(dp[ei][ej] == dp[ei-1][ej]) ei--;
            else
            {
                ans.push_back({all[ei-1].first / all[ei-1].second, all[ei-1].second});
                ej -= all[ei-1].first;
                ei--;
            }
        }
        sort(ans.rbegin(), ans.rend());
        string res;
        for(const auto& p : ans) res += string(p.second, static_cast<char>(p.first + '0'));
        return res;
    }
};

混合背包

1.P1833 樱花

题目大意:

背包容量W,有n种物品,有一些物品有无限个,有一些物品只有一个,还有一些物品有大于一个的有限个,求背包容纳的最大价值。

混合背包问题只需要分情况,无限个的物品,套用完全背包模板,有限个的物品,如果大于1个就用二进制优化,转化成01背包。都可以用一维dp去解决。

#include <bits/stdc++.h>
using namespace std;

const int MAXW = 1001;
const int MAXN = 10001;
const int MAXSUMK = 1e6 + 5;

struct Node
{
    int w;
    int v;
    int k;
};

int main()
{
    int h1, m1, h2, m2, n;
    scanf("%d:%d %d:%d %d\n", &h1, &m1, &h2, &m2, &n);
    int w = h2 * 60 + m2 - h1 * 60 - m1;
    Node all[MAXSUMK];
    int count = 0;
    for(int i = 0; i < n; i++)
    {
        int wi, vi, ki;
        scanf("%d%d%d", &wi, &vi, &ki);
        //01背包或者完全背包
        if(ki == 0 || ki == 1) all[count++] = {wi, vi, ki};
        else
        {
            //多重背包,二进制优化
            int c = 1;
            while(ki >= c)
            {
                ki -= c;
                all[count++] = {wi*c, vi*c, 1};
                c *= 2;
            }
            if(ki > 0) all[count++] = {wi*ki, vi*ki, 1};
        }
    }
    int dp[MAXW] = {0};
    for(int i = 0; i < count; i++)
    {
        if(all[i].k == 0)
        {
            //完全背包模板
            for(int j = all[i].w; j <= w; j++) 
            {
                dp[j] = max(dp[j], dp[j-all[i].w] + all[i].v);
            }
        }
        else
        {
            //01背包模板
            for(int j = w; j >= all[i].w; j--)
            {
               dp[j] = max(dp[j], dp[j-all[i].w] + all[i].v);
            }
        }
    }
    printf("%d\n", dp[w]);
    return 0;
}

分组背包

1. P1757 通天之分组背包

题目大意:

背包容量W,有n件物品,每件物品重量wi, 价值vi,属于第 gi 组,同一组的物品只能取一件。求背包能容纳的最大价值。

分组背包问题,要求每一组的物品只能选1件,就要把每一组看成01背包中的一件物品,然后再在组内选择一件使得总价值最大。首先要想到合适的数据结构来存储物品,因为有分组的存在,所以属于第 gi 组的物品应该存放在 all[gi][cnti] 里,之后,把每一组看成一件物品,再在组内选择一件。核心代码:

    for(int i = 1; i <= maxg; i++) // 循环每一组
    {
        for(int j = w; j >= 0; j--) // 循环背包容量
        {
            for(int k = 0; k < count[i]; k++) // 循环该组的每一个物品
            {
                if(j >= all[i][k].w)
                {
                    dp[j] = max(dp[j], dp[j-all[i][k].w] + all[i][k].v);
                }
            }
        }
    }

要注意循环顺序,因为这里是每一组看成一件物品,所以和01背包一样,外层循环是每一组,内层循环是从总容量w逆序遍历,但是因为这里每一组里的重量并不相同,所以遍历到0,再对同一组里的每一个元素进行 dp[j] = max(dp[j], dp[ j - w[i][k] ] + v[i][k])的计算。

#include <bits/stdc++.h>
using namespace std;

const int MAXG = 1010;
const int MAXN = 1010;
const int MAXW = 1010;

struct Node
{
    int w;
    int v;
};

int main()
{
    int w, n;
    scanf("%d%d", &w, &n);
    Node all[MAXG][MAXN] = {0};
    int count[MAXG] = {0};
    int maxg = 0;
    for(int i = 0; i < n; i++)
    {
        int wi, vi, gi;
        scanf("%d%d%d", &wi, &vi, &gi);
        all[gi][count[gi]++] = {wi, vi};
        maxg = max(maxg, gi);
    }
    int dp[MAXW] = {0};
    for(int i = 1; i <= maxg; i++)
    {
        for(int j = w; j >= 0; j--)
        {
            for(int k = 0; k < count[i]; k++)
            {
                if(j >= all[i][k].w)
                {
                    dp[j] = max(dp[j], dp[j-all[i][k].w] + all[i][k].v);
                }
            }
        }
    }
    printf("%d\n", dp[w]);
    return 0;
}

有依赖的背包

1.P1064 金明的预算方案

题目大意:

背包容量W,有m件物品,重量wi, 价值vi, 分为主件和附件,一个主件最多有2个附件。如果选择了附件那么必须也要选择主件。求背包能容纳的最大价值。

因为选附件时必须要选主件,假设主件为A,附件为B C,那么相当于 A AB AC ABC 这四种里只能选择一种,可以转换为分组背包问题。

题目的数据处理比较繁琐,首先要找到属于同一组的,再对每一组的物品进行组合得到所有可供选择的物品。同时注意题目里的价值是 pi * wi。

#include <bits/stdc++.h>
using namespace std;

const int MAXN = 3201;
const int MAXG = 65;

struct Node
{
    int w;
    int v;
    int q;
};

int main()
{
    int w, m;
    scanf("%d%d", &w, &m);
    //价格都是10的倍数,可以减小时间复杂度。
    w /= 10;
    Node groups[MAXG][3];
    int indexs[MAXG] = {0};
    //先找到每一组里包含哪些元素
    for(int i = 1; i <= m; i++)
    {
        int wi, vi, qi;
        scanf("%d%d%d", &wi, &vi, &qi);
        wi /= 10;
        if(qi == 0) groups[i][indexs[i]++] = {wi, vi * wi, 0};
        else groups[qi][indexs[qi]++] = {wi, vi * wi, qi};
    }
    //一个商品最多有2个附件,也就是一组内最多有A AB AC ABC 四种选择
    Node all[MAXG][4];
    int count[MAXG] = {0};
    for(int i = 1; i <= m; i++)
    {
        if(indexs[i] > 0)
        {
            int master = -1, slave1 = -1, slave2 = -1;
            for (int j = 0; j < indexs[i]; ++j)
            {
                if(groups[i][j].q == 0) master = j;
                else if(slave1 == -1) slave1 = j;
                else slave2 = j;
            }
            all[i][count[i]++] = {groups[i][master].w, groups[i][master].v , 0};
            if(slave1 > -1)
            {
                int totalw = groups[i][master].w + groups[i][slave1].w;
                int totalv = groups[i][master].v + groups[i][slave1].v ;
                all[i][count[i]++] = {totalw, totalv, 0};
            }
            if(slave2 > -1)
            {
                int totalw = groups[i][master].w + groups[i][slave2].w;
                int totalv = groups[i][master].v + groups[i][master].w + groups[i][slave2].v + groups[i][slave2].w;
                all[i][count[i]++] = {totalw, totalv, 0};
                totalw += groups[i][slave1].w;
                totalv += groups[i][slave1].v * groups[i][slave1].w;
                all[i][count[i]++] = {totalw, totalv, 0};
            }
        }
    }
    //分组背包模板
    int dp[MAXN] = {0};
    for(int i = 1; i <= m; i++)
    {
        for(int j = w; j >= 0; j--)
        {
            for(int k = 0; k < count[i]; k++)
            {
                if(j >= all[i][k].w)
                {
                    dp[j] = max(dp[j], dp[j-all[i][k].w] + all[i][k].v);
                }
            }
        }
    }
    printf("%d\n", dp[w] * 10);
    return 0;
}

 

树上背包问题 树上背包问题树上背包问题算法原理例题一:有依赖的背包问题题意数据范围思路代码例题二:二叉苹果树题意数据范围思路代码例题三:Factories(2018icpc宁夏网络赛)题意数据范围思路代码 树上背包问题 一些题目给定了树形结构,在这个树形结构中选取一定数量的点或边(也可能是其他属性),使得某种与点权或者边权相关的花费最大或者最小。解决这类问题,一般要考虑使用树上背包。 算法原理 树上背包,顾名思义,就是在树上做背包问题。一个节点的若干子树可以看作是若干组背包,也就是用树形dp的方式做分组背包问题。一般来说 阅读详情

相关推荐

01背包问题-四种方法

01背包问题,四种方法(动态规划、贪心、回溯、分支限界)

背包问题算法全解析:动态规划贪心算法详解

计算机背包问题是动态规划算法中的经典问题。本文将从理论实践两个方面深入探讨计算机背包问题,并通过实际案例分析,帮助读者更好地理解应用该问题。

BASK2311的博客 6459

背包问题解决方法总结

题目 有N件物品一个容量为V的背包。第i件物品的重量是c[i],价值是w[i]。求解将哪些物品装入背包可使价值总最大。 基本思路有好几种方法,请参考资源文件

01背包问题的多种求解

关于01背包问题的多种解法

m0_64862358的博客 5170

0-1 背包问题的 4 种解决方法&&算法策略

蛮力法 递归与分治策略 动态规划 贪心算法 回溯法 分支限界法 前言 0-1背包是一个经典的问题,而它能用不同的算法思想去解决。恰巧最近在看算法,学习算法就是学习解决问题的思路。现在将0-1背包问题与解决方法整理出来,这样不仅能区分不同的算法思想,还能加深对0-1背包问题的理解。虽然有的算法思想并不能解决这一问题,但是为了对算法策略有一个较为整体的了解,所以在这里做一下简单的介绍。 ...

Tyler_Zx的博客 6万+

九种 0-1 背包问题详解

目录 动态规划概念 问题1:0-1背包问题 问题2:完全背包问题 问题3:多重背包问题 问题4:混合背包问题 问题5:二维背包问题 问题6:分组背包问题 问题7:有依赖的背包问题(困难) 问题8:背包问题求方案数 问题9:背包问题求具体方案 前言 0-1 背包是一个经典的问题,之前也整理过一篇关于 0-1 背包的博客,当时只是整理了 0-1 背包问题的4 种解决方法。最近在复习算法,发现有很多 0-1 背包问题的衍生问题。0-1 背包问题的限制条件既可以是重量,也可以是容量,或者..

Tyler_Zx的博客 2万+

算法背包问题的五种方法

啊,哈喽,小伙伴们大家好。我是#张亿,今天呐,学的是动态规划算法——

#张亿的博客 800

MATLAB 背包问题

本文介绍了 MATLAB 中的背包问题,包括 0-1 背包问题、完全背包问题、多重背包问题等。

小嗷犬的博客 4514

【动态规划算法】背包问题——0/1背包问题,多目标优化背包问题详解与示例

背包问题是动态规划中的一个经典问题,通常有两种主要变种:0/1 背包问题背包问题(Fractional Knapsack Problem)。这里我们先详细解释0/1背包问题再介绍背包问题的变种。

qq_35831906的博客 1万+

背包问题总结

前面是转载来的背包9讲,非常详细,后面有几个lintcode上的题目 前言 本篇文章是我(dd_engi)正在进行中的一个雄心勃勃的写作计划的一部分,这个计划的内容是写作一份较为完善的NOIP难度的动态规划总结,名为《解动态规划题的基本思考方式》。现在你看到的是这个写作计划最先发布的一部分。 背包问题是一个经典的动态规划模型。它既简单形象容易理解,又在某种程度上能够揭示动态规划的本质,故不少...

William Zhao's notes 2万+

经典的背包问题九讲,必看

背包九讲,经典的背包问题讲解,不用做过多介绍,必看

01背包问题

01背包问题 商店的货架上摆放着不同重量价值的商品,一个小偷在商店行窃,他携带的背包只能装固定重量的商品。装哪些商品才能获得最大的收益呢?在限定条件内找到最佳的物品组合,这样的问题统称为背包问题。 根据限定的条件不同,背包问题还可以细分: 部分背包问题:所有物品是可再分的,即允许将某件物品的一部分(例如 1/3)放入背包; 0-1 背包问题:所有物品不可再分,要么整个装入背包,要么放弃,不允许出现“仅选择物品的 1/3 装入背包”的情况; 完全背包问题:不对每一件物品的数量做限制,同一件物品可以选择多个装

ccc369639963的博客 2万+

【dp】背包问题

背包问题是⼀种组合优化的问题。问题可以描述为:给定⼀组物品,每种物品都有自己的重量价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。01背包问题:每个物品只有⼀个完全背包问题:每个物品有无限多个多重背包问题:每件物品最多有 x 个混合背包问题:每个物品会有上面三种情况分组背包问题:物品有 n 组,每组物品里有若干个,每组里最多选⼀个物品不一定装满背包背包一定装满限定条件只有⼀个:比如体积 -> 普通的背包问题限定条件有两个:比如体积 + 重量 -> 二维费用背包问题

YoungMLet的博客 2189

经典背包问题

一、背包问题的描述 背包问题可以有多种形式,下面将对其逐一进行描述: (1)经典的0-1背包问题(无物品的价值): 假设有一个能装入容量为C的背包n件重量分别为w1,w2,,...,wn的物品,能否从n件物品中挑选若干件恰好装满背包,要求找出所有满足上述条件的解。 当C=10,各件物品重量为{1,8,4,3,5,2}时,可以找到下列4组解:(1,4,3,2)、(1,4,5)、(8,...

qq_40427276的博客 5199

【动态规划】背包问题 {01背包问题;完全背包问题;二维费用背包问题}

【动态规划】背包问题 {01背包问题;完全背包问题;二维费用背包问题}

52Herzt的博客 5512

01背包问题VS完全背包问题

01背包问题: 每个物品只能选择一次,给定背包的最大承重量total每个物品i的重量weight[i]价值value[i],求背包可以装下的最大价值 #include<iostream> #include<vector> using namespace std; int main() { int num, total; cin >> num &...

weixin_40679061的博客 3962

背包问题模型总结

背包模型总结汇总。

涛涛的个人博客 4648

几种背包问题总结

01背包问题(物品选不选) 完全背包问题(物品无限选) 多重背包问题(物品个数有限制且不同) 混合背包问题 二维费用的背包问题(重量+体积) 分组背包问题(先分组,组内互斥) 背包问题求方案数 一般是价值,这里是方案数 求背包问题的方案 有依赖的背包问题 ...

tianyouououou的博客 4263
上一篇: leetcode中归并排序的应用
下一篇: 单调栈
HackingGate
博客等级 码龄12年 13粉丝 196原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值