目录
01背包概述:
01背包是经典的动态规划的解法
完整的纯01背包代码
几道01背包的应用题
1.分割等和子集
1,回溯法
2,dp法
2.最后一块石头的重量2
3.目标和
1,回溯法
2.dp法
01背包问题为有n种物品,每种物品只有一个,每个物品都有自己的价值和重量,有一个最多能放重量为m的背包,问:这个背包最多能装多少价值的物品.
首先,这个问题不能用简单的贪心解决,因为如果仅仅每次放价值最大的物品,因为能放的最大重量有限,放多个价值较小但重量较轻的物品可能会更优.
w v
物品0 1 15
物品1 3 20
物品2 4 30
1,dp[j]含义:容量为j的背包所能容纳的最大价值
2.递推公式:再放i物品的时候有放和不放两种状态
不放i物品:包里价值为之前的dp[j]
放i物品:包里价值为dp[j-weight[i]]+value[i],相当于放了i物品后除去i物品空间的背包能装的最大价值加上i物品的价值(前提是放的下i,要求是j>=weight[i])
两种情况的最大值就是dp[j]的新值.故递推公式为dp[j]=max(dp[j],dp[j-weight[i]]+value[i]);
3.初始化:物品的价值为整数,全都赋为0即可
4.遍历顺序:物品从前向后遍历即可,而背包容量要从后向前遍历,因为从前向后遍历的话:如果背包容量够大,它会装下多个物品0,这并不是我们想要的
5.举例推导dp数组
由上例
放物品0 : 0 1 2 3 4
0 15 15 15 15 放物品1 :
0 15 15 20 35 放物品2:
0 15 15 20 35
自己推导一遍就会明白从后向前遍历背包容量,dp[j-weight[i]]都是之前的状态,还未更新,所以不会重复放某个物品
#include
using namespace std;
int main()
{
int weight[5005],value[5005],dp[5005]={0};//dp[j]:容量为j的背包所能装的最大价值
int n,m;
cin>>m>>n;
for(int i=0;i>weight[i];
for(int i=0;i>value[i];
//for(int j=weight[0];j<=n;j++)
//dp[0][j]=value[0];
for(int i=0;i=weight[i];j--)
{
dp[j]=max(dp[j],dp[j-weight[i]]+value[i]);
}
}
cout<
分割等和子集
暴力方法当然是回溯法:枚举每种组合,看是否有组合的和等于所有元素总和的一半,非常容易可以知道如果总和为奇数,就不可能凑出等和的两部分,由此剪枝.但还是超时了.
class Solution {
public:
int tol=0,sum=0,flag=0;
void dfs(int startindex,vector&nums)
{
if(sum==tol/2)
{
flag=1;
return;
}
for(int i=startindex;i& nums) {
for(int i=0;i
01背包的解法就是容量为总和一半的背包可不可以恰好装满来判定是否可以分成和相等的两部分
class Solution {
public:
bool canPartition(vector& nums) {
int tol=0,dp[20005]={0};
for(int i=0;i=nums[i];j--)
{
dp[j]=max(dp[j],dp[j-nums[i]]+nums[i]);
}
}
if(dp[tol/2]==tol/2)return true;
else return false;
}
}
};
最后一块石头的重量2
这题与上一题类似,但这一题求的是容量为总重量一半的背包最多能装多少,使两堆石头差别尽可能小
class Solution {
public:
int lastStoneWeightII(vector& stones) {
int dp[1505]={0},tol=0;
for(int i=0;i=stones[i];j--)
{
dp[j]=max(dp[j],dp[j-stones[i]]+stones[i]);
}
}
int mid=tol/2;
return tol-2*dp[mid];
}
};
目标和
class Solution {
public:
int sum=0,cnt=0;
void dfs(int startindex,vector&nums,int s)
{
if(sum==s)
{
cnt++;
}
for(int i=startindex;i& nums, int target) {
int tol=0;
for(int i=0;itol)return 0;
if((target+tol)%2)return 0;
int s=(target+tol)/2;
sort(nums.begin(),nums.end());//方便剪枝,sum+nums[i]<=s
dfs(0,nums,s);
return cnt;
}
};
这题可以转化成装满容量为j的背包有多少种方法
把数字分为总和分别为a,b的两堆
sum=a+b,target=a-b,
所以sum+target=2a,题目就转化成有多少个组合的总和为a,即装满容量为a的背包有多少种方法
这与前两题不同
1,dp[j]含义:装满容量为j的背包有多少种方法
2,递推公式:dp[j]+=dp[j-weight[i]]因为要装下i物体,就要找装满容量为j-weight[i]的背包的方法数是多少,所有的dp[j]累加起来即可.
3.初始化.dp[0]=1;其他的为0,因为用一个背包装一个物体恰好装满是加的dp[0],这算一种方法
4,遍历顺序,与01背包相同,物品从前向后,背包从后向前
5.举例推导dp数组
nums = [1,1,1,1,1], target = 3s=(5+3)/2=4;
第一个数1
1 1 0 0 0 第二个数1
1 2 1 0 0 第三个数1
1 3 3 1 0 第四个数1
1 4 6 4 1 第五个数1
1 5 10 10 5 所以答案为dp[4]=5;
class Solution {
public:
int findTargetSumWays(vector& nums, int target) {
int tol=0,dp[11000]={0};
for(int i=0;itol)return 0;
if((target+tol)%2)return 0;
int s=(target+tol)/2;
dp[0]=1;
for(int i=0;i=nums[i];j--)
{
dp[j]+=dp[j-nums[i]];
}
}
return dp[s];
}
};