赞
踩
目录
背包问题 (Knapsack problem) 是⼀种组合优化的 NP完全问题。
问题可以描述为:给定⼀组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。
【模板】01背包_牛客题霸_牛客网 (nowcoder.com)
#:状态表示:
根据「经验 + 题目要求」我们继续尝试「用 i 位置为结尾」结合「题目要求」得到,dp[ i ]表示:从前 i 个物品中挑选,总体积不超过 j ,所有选法中,能挑选出来的最大价值。(也看出来划了杠,看似很完美,但其实是错的),因为一个背包是有体积的,有体积为关键的背包仅仅看第几个物品是没用的。
我们需要将背包的体积也需要包含进去,d[ i ][ j ]表示:从前 i 个物品中挑选,总体积不超过 j ,所有选法中,能挑选出来的最大价值。
#:状态转移方程:
综上,状态转移方程为:dp[ i ][ j ] = max(dp[ i - 1 ][ j ], dp[ i - 1 ][ j - v[ i ] ] + w[ i ])。
#:初始化:
我们多加⼀行,方便我们的初始化,此时仅需将第⼀行初始化为 0 即可。因为什么也不选,也能满足体积不小于 j 的情况,此时的价值为 0 。
#:填表顺序:
根据「状态转移方程」,我们仅需「从上往下」填表即可。
#:返回值:
根据「状态表示」,返回 dp[n][V] 。
#:状态表示:
根据「经验 + 题目要求」我们继续尝试「用 i 位置为结尾」结合「题目要求」得到,dp[ i ][ j ] 表示:从前 i 个物品中挑选,总体积「正好」等于 j ,所有的选法中,能挑选出来的最⼤价值。
#:状态转移方程:
但是在使用 dp[ i - 1 ][ j - v[ i ] ] 的时候,不仅要判断 j >= v[ i ] ,⼜要判断 dp[ i - 1 ][ j - v[ i ] ] 表示的情况是否存在,也就是 dp[ i - 1 ][ j - v[ i ] ] != -1 。
#:初始化:
我们多加一行,方便我们的初始化:
#:填表顺序:
根据「状态转移方程」,我们仅需「从上往下」填表即可。
#:返回值:
由于最后可能凑不成体积为 V 的情况,因此返回之前需要「特判」⼀下。
- #include <iostream>
- #include <vector>
- #include <utility>
- #include <algorithm>
- #include <memory.h>
- using namespace std;
-
- int main()
- {
- int n = 0, V = 0;
- cin >> n >> V;
- vector<pair<int, int>> val;
- val.reserve(n + 1);
- val.push_back(make_pair(0, 0));
- for (int i = n; i > 0 ; i--) {
- int v = 0, w = 0;
- cin >> v >> w;
- val.push_back(make_pair(v, w));
- }
-
- // 背包不满的时候的最大价值
- // 1、创建dp表
- vector<vector<int>> dp(n + 1, vector<int>(V + 1, 0));
-
- // 2、初始化
-
- // 3、填表
- for (int i = 1; i < n + 1; i++)
- {
- for (int j = 1; j < V + 1; j++)
- {
- dp[i][j] = dp[i - 1][j];
-
- if (j >= val[i].first)
- dp[i][j] = max(dp[i][j],
- dp[i - 1][j - val[i].first] + val[i].second);
- }
- }
-
- // 4、返回值
- cout << dp[n][V] << endl;
-
-
- // 背包满的时候的最大价值
- // 1、创建dp表
- fill(dp.begin(), dp.end(), vector<int>(V + 1, 0));
-
- // 2、初始化
- for (int i = 1; i < V + 1; i++)
- dp[0][i] = -1;
-
- // 3、填表
- for (int i = 1; i < n + 1; i++)
- {
- for (int j = 1; j < V + 1; j++)
- {
- dp[i][j] = dp[i - 1][j];
-
- if (j >= val[i].first && dp[i - 1][j - val[i].first] != -1)
- dp[i][j] = max(dp[i][j],
- dp[i - 1][j - val[i].first] + val[i].second);
- }
- }
-
- // 4、返回值
- cout << (dp[n][V] == -1 ? 0 : dp[n][V]) << endl;
-
- return 0;
- }
首先根据状态方程:dp[ i ][ j ] = max(dp[ i - 1 ][ j ], dp[ i - 1 ][ j - v[ i ] ] + w[ i ]),可以发现只是和两行有关,所以我们就可以首先用两个一维数组解决。
由前一行的两个元素推出需求的位置。根据此我们可以推出一个一维数组解决,只不过有一个问题
所以,防止在未使用的时候出现被更改的情况,建议以从右往左的顺序进行。
- #include <iostream>
- #include <vector>
- #include <utility>
- #include <algorithm>
- #include <memory.h>
- using namespace std;
-
- int main()
- {
- int n = 0, V = 0;
- cin >> n >> V;
- vector<pair<int, int>> val;
- val.reserve(n + 1);
- val.push_back(make_pair(0, 0));
- for (int i = n; i > 0 ; i--) {
- int v = 0, w = 0;
- cin >> v >> w;
- val.push_back(make_pair(v, w));
- }
-
- // 背包不满的时候的最大价值
- // 1、创建dp表
- vector<int> dp(V + 1, 0);
-
- // 2、初始化
-
- // 3、填表
- for (int i = 1; i < n + 1; i++)
- {
- for (int j = V; j >= val[i].first; j--)
- {
- if (j >= val[i].first)
- dp[j] = max(dp[j], dp[j - val[i].first] + val[i].second);
- }
- }
-
- // 4、返回值
- cout << dp[V] << endl;
-
-
- // 背包满的时候的最大价值
- // 1、创建dp表
- fill(dp.begin(), dp.end(), -1);
-
- // 2、初始化
- dp[0] = 0;
-
- // 3、填表
- for (int i = 1; i < n + 1; i++)
- {
- for (int j = V; j >= val[i].first; j--)
- {
- if (dp[j - val[i].first] != -1)
- dp[j] = max(dp[j], dp[j - val[i].first] + val[i].second);
- }
- }
-
- // 4、返回值
- cout << (dp[V] == -1 ? 0 : dp[V]) << endl;
- return 0;
- }
优化细节:
因为如果出现 j >= val[i].first;那么 max(dp[j], dp[j - val[i].first] + val[i].second);没有执行的意义会越界。
未优化的时候因为需要 dp[ i ][ j ] = dp[i - 1][ j ];所以必须将第二层循环迭代完成,但是此处由于已经优化为一维数组,所以无需继续迭代。
对于类01背包问题
- 转换:对应的题目提供的要素,时常是需要一个转换过程的(十分重要)。
重点在于每一个元素的选和不选的问题,也就是01背包关键的地方。
- 01背包 "模板":不是说里面的东西都是照抄过来的,而是里面的分析思路 "模板",根据题目进行一定方式的微调。
根据前两个性质,我们可以提前判断数组能够被划分,根据最后⼀个性质,我们发现问题就转化成了「01 背包」的模型:
其中,数组内的元素就是物品,总和就是背包。 那么我们就可以⽤背包模型的分析方式,来处理这道题。 「不要背」01背包的状态转移方程,我们要记住的是分析问题的模式,用这种分析问题的模式来解决问题。
#:状态表示:
根据「经验 + 题目要求」我们继续尝试「用 i 位置为结尾」结合「题目要求」得到,dp[ i ][ j ] 表示在前 i 个元素中选择,所有的选法中,能否凑成总和为 j 这个数。
#:状态转移方程:
根据「最后⼀个位置」的元素,结合题目的要求,分情况讨论:
综上所述,两种情况下只要有⼀种能够凑成总和为 j ,那么这个状态就是 true 。因此,状态转移方程为:dp[ i ][ j ] = dp[ i - 1 ][ j ];if(nums[ i ] <= j) dp[ i ][ j ] = dp[ i ][ j ] || dp[ i - 1 ][ j - nums[ i ]]。
#:初始化:
#:填表顺序:
根据「状态转移方程」,我们需要「从上往下」填写每一行,每一行的顺序是「无所谓的」。
#:返回值:
根据「状态表示」,返回 dp[ n ][ aim ] 的值。其中 n 表示数组的大小, aim 表示要凑的目标和。
- class Solution {
- public:
- bool canPartition(vector<int>& nums) {
-
- int n = nums.size();
- int sum = 0;
- for(auto val : nums)
- sum += val;
-
- if(sum % 2) return false;
-
- // 1、创建dp表
- vector<vector<bool>> dp(n + 1, vector<bool>(sum + 1, false));
-
- // 2、初始化
- for(int i = 0; i < n; i++)
- dp[i][0] = true;
-
- // 3、填表
- for(int i = 1; i < n + 1; i++)
- {
- for(int j = 1; j < sum + 1; 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]];
- }
- }
-
- // 4、返回值
- return dp[n][sum / 2];
- }
- };
原理与第一道经典的01背包问题一样。
- class Solution {
- public:
- bool canPartition(vector<int>& nums) {
-
- int n = nums.size();
- int sum = 0;
- for(auto val : nums)
- sum += val;
-
- if(sum % 2) return false;
-
- // 1、创建dp表
- vector<bool> dp(sum + 1, false);
-
- // 2、初始化
- dp[0] = true;
-
- // 3、填表
- for(int i = 1; i < n + 1; i++)
- {
- for(int j = sum + 1; j >= nums[i - 1]; j--)
- dp[j] = dp[j] || dp[j - nums[i - 1]];
- }
-
- // 4、返回值
- return dp[sum / 2];
- }
- };
#:状态表示:
根据「经验 + 题目要求」我们继续尝试「用 i 位置为结尾」结合「题目要求」得到,dp[ i ][ j ] 表示:在前 i 个数中选,总和正好等于 j ,⼀共有多少种选法。
#:状态转移方程:
综上所述,两种情况如果存在的话,应该要累加在⼀起。因此,状态转移方程为:dp[ i ][ j ] = dp[ i - 1 ][ j ]; if(nums[ i ] <= j) dp[ i ][ j ] += dp[ i - 1 ][ j - nums[ i ]]。
#:初始化:
#:填表顺序:
根据「状态转移方程」,我们需要「从上往下」填写每一行,每一行的顺序是「无所谓的」。
#:返回值:
根据「状态表示」,返回 dp[ n ][ aim ] 的值。其中 n 表示数组的大小, aim 表示要凑的目标和。
- class Solution {
- public:
- int findTargetSumWays(vector<int>& nums, int target) {
- int sum = 0;
- for(auto val : nums)
- sum += val;
-
- int aim = (sum + target) / 2;
- if(aim < 0 || (sum + target) % 2) return 0;
-
- int n = nums.size();
-
- // 1、创建dp表
- vector<vector<int>> dp(n + 1, vector<int>(aim + 1, 0));
-
- // 2、初始化
- dp[0][0] = 1;
-
- // 3、填表
- for(int i = 1; i < n + 1; i++)
- {
- for(int j = 0; j < aim + 1; j++)
- {
- dp[i][j] = dp[i - 1][j];
- if(j >= nums[i - 1])
- dp[i][j] += dp[i - 1][j - nums[i - 1]];
- }
- }
-
- // 4、返回值
- return dp[n][aim];
- }
- };
原理与第一道经典的01背包问题一样。
- class Solution {
- public:
- int findTargetSumWays(vector<int>& nums, int target) {
- int sum = 0;
- for(auto val : nums)
- sum += val;
-
- int aim = (sum + target) / 2;
- if(aim < 0 || (sum + target) % 2) return 0;
-
- int n = nums.size();
-
- // 1、创建dp表
- vector<int> dp(aim + 1, 0);
-
- // 2、初始化
- dp[0] = 1;
-
- // 3、填表
- for(int i = 1; i < n + 1; i++)
- {
- for(int j = aim; j >= nums[i - 1]; j--)
- dp[j] += dp[j - nums[i - 1]];
- }
-
- // 4、返回值
- return dp[aim];
- }
- };
1049. 最后一块石头的重量 II - 力扣(LeetCode)
先将问题「转化」成我们熟悉的题型。
#:状态表示:
根据「经验 + 题目要求」我们继续尝试「用 i 位置为结尾」结合「题目要求」得到,dp[ i ][ j ] 表示在前 i 个元素中选择,总和不超过 j,此时所有元素的「最大和」。
#:状态转移方程:
综上所述,我们要的是最大价值。因此,状态转移方程为:dp[ i ][ j ] = dp[ i - 1 ][ j ]; if(j >= stones[ i ]) dp[ i ][ j ] = max(dp[ i ][ j ], dp[ i - 1 ][ j - stones[ i ]] + stones[ i ]); 。
#:初始化:
#:填表顺序:
根据「状态转移方程」,我们需要「从上往下」填写每一行,每一行的顺序是「无所谓的」。
#:返回值:
- class Solution {
- public:
- int lastStoneWeightII(vector<int>& stones) {
-
- int n = stones.size();
- int sum = 0;
- for(auto val : stones)
- sum += val;
- int half = sum / 2;
-
- // 1、创建dp表
- vector<vector<int>> dp(n + 1, vector<int>(half + 1, 0));
-
- // 2、初始化 -- 已经在创建dp表中初始化
-
- // 3、填表
- for(int i = 1; i < n + 1; i++)
- {
- for(int j = 1; j < half + 1; j++)
- {
- dp[i][j] = dp[i - 1][j];
- if(j >= stones[i - 1])
- dp[i][j] = max(dp[i][j], dp[i - 1][j - stones[i - 1]] + stones[i - 1]);
- }
- }
- return sum - 2 * dp[n][half];
- }
- };
原理与第一道经典的01背包问题一样。
- class Solution {
- public:
- int lastStoneWeightII(vector<int>& stones) {
-
- int n = stones.size();
- int sum = 0;
- for(auto val : stones)
- sum += val;
- int half = sum / 2;
-
- // 1、创建dp表
- vector<int> dp(half + 1, 0);
-
- // 2、初始化 -- 已经在创建dp表中初始化
-
- // 3、填表
- for(int i = 1; i < n + 1; i++)
- {
- for(int j = half; j >= stones[i - 1]; j--)
- dp[j] = max(dp[j], dp[j - stones[i - 1]] + stones[i - 1]);
- }
- return sum - 2 * dp[half];
- }
- };
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。