目录
背包DP
背包问题分为01背包与完全背包
01背包,共有N个物体,每个物体只有一个,装入给定背包中
完全背包,共有N种物体,每个物体数量不限,装入给定背包中
01背包
重点:
1.思路:对每个物体i,在剩余容量j时选择装与不装
2.注意一维情况时的容量倒序遍历
问题定义:给定容量V的背包,和体积分别为{Ci}(i=1...N)的N个物体,每个物体价值为{Wi}(i=1..N).求使得背包价值最大的装法。
状态:对于每个物体i,在背包容量为j时,背包价值为DP(i,j)
选择:
如果当前背包体积j>=Ci,有装与不装当前物体两种选择
选择装入当前物体,DP(i,j)=DP(i-1,j-Ci)+Wi (j>=Ci)
不装入当前物体, DP(i,j)=DP(i-1,j)
如果剩余背包体积<Ci,则只能不装当前物体
DP(i,j)=DP(i-1,j)
故转移方程:
for i=1....N
for j=0...V
if(j>=Ci) DP(i,j)=max{DP(i-1,j),DP(i-1,j-Ci)+Wi};
if(j<Ci) DP(i,j)=DP(i-1,j)
优化为1维数组
由原始递推式,DP(i,j)仅仅依赖于DP(i-1,XX)状态,考虑优化为1维数组。由于计算DP(i,j)时需要保证DP(i-1,j-Ci)还没有更新,因此容量需要倒序遍历。
for i=1....N
for j=V...0 //此处注意:由于在计算DP(j)时需要DP(j-Ci)依然保持在i-1的状态,所以V需要倒序遍历
if(j>=Ci)DP(j)=max{DP(j),DP(j-Ci)}
01背包其他问法:根据问法更改转移方程的函数
<1>是否能恰好装满
二维状态定义:DP(i,j)代表第i个物体容量为j时是否恰好装满
二维状态转移方程:
DP(i,j)=DP(i-1,j)||DP(i-1,j-Ci) (j>=Ci)
DP(i,j)=DP(i-1,j) (j<Ci)
一维状态转移方程:
DP(j)=DP(j)||DP(j-Ci) (j>=Ci)(j=V....0) //注意01背包问题1维状态需要倒序遍历
初始化
DP[0]=TRUE //容量为0时一个物体都不选恰好能装满,故DP[0]=1;
DP[1....V]=FALSE
<2>恰好装满的方案总数
二维状态定义:DP(i,j)代表第i个物体容量为j时恰好装满的总方案数
二维状态转移方程:
DP(i,j)=DP(i-1,j)+DP(i-1,j-Ci) (j>=Ci)
DP(i,j)=DP(i-1,j) (j<Ci)
一维数组状态:
DP(j)=DP(j)+DP(j-Ci) (j>=Ci)(j=V....0) //注意01背包问题1维状态需要倒序遍历
初始化
DP[0]=1 //容量为0时至少存在 一个物体都不选这个方案,故初始化为1
DP[1....V]=0
<3>恰好装满所需最少物体数
二维状态定义:DP(i,j)代表第i个物体容量为j时恰好装满所需最少物体数,物体数方案不存在返回-1
二维状态转移方程:
DP(i,j)=min{DP(i-1,j),DP(i-1,j-Ci)+1}(j>=Ci)
DP(i,j)=DP(i-1,j) (j<Ci)
一维数组状态:
DP(j)=min{DP(j),DP(j-Ci)} (j=V....0) //注意01背包问题1维状态需要倒序遍历
初始化
DP[0]=0 //容量为0时一个都不选,所需物体数最少
DP[1....V]=V+1 //所需物体数一定<=V,V+1代表初始化为未找到有效方案状态
完全背包
问题定义:给定n种物体,每种物体数量不限,每种物体的体积为Ci,价值为Wi。
向容量为V的背包中装入这n种物体,求背包能装入的最大价值。与01背包的最大不同是,此时每种物体的数量不限。
状态定义:在对第i种物体进行选择,背包体积为j时。DP(i,j)表示此时背包最大价值
选择:
当前物体体积小于背包容积j,则可选择装入或不装入
装入:DP(i,j)=DP(i,j-Ci)+Wi //此处与01背包最大不同是,由于选择装入物体i后,可以选择继续装入物体i。故此处为DP(i,j-Ci)而不是DP(i-1,j-Ci)
不装入:DP(i,j)=DP(i-1,j)
当物体i体积大于j,只能选择不装入
DP(i,j)=DP(i-1,j)
状态转移方程:
DP(i,j)=max{DP(i-1,j),DP(i,j-Ci)+Wi} (j>=Ci)
DP(i,j)=DP(i-1,j) (j<Ci)
完全背包一维优化
由状态转移方程,DP(i,j)最多依赖于DP(i-1,j),且计算DP(i,j)时需要DP(i,j-Ci)已经计算。
故一维状态方程
for i=1.....N
for j=Ci....V //为保证j>=Ci ,j从Ci开始遍历
DP(j)=max{DP(j),DP(j-Ci)+Wi} //注意此时对容量遍历是正序遍历,与01背包正好相反
其他问法:
完全背包也包括恰好装满方案数、装满所需最少数量、能否恰好装满等问法。
leetcode 练习
416分割等和子集333(0-1背包,是否恰好能装下)
bool canPartition(vector<int>& nums) {
if(nums.empty()){
return false;
}
unsigned int sum=0;
for(auto i:nums) sum+=i;
if(sum%2!=0) return false;
//问题转化为在nums中是否存在n个数之和恰好为sum/2
//转化为01背包问题,求是否能恰好装满
unsigned int target=sum/2;
vector<vector<bool>>dp(nums.size()+1,vector<bool>(target+1,false));
dp[0][0]=true; //dp[0][0]代表一个物体都不选,恰好能装满容量为0背包
//转移方程
//DP(i,j)=DP(i-1,j)||DP(i-1,j-Ci) j>=Ci
//DP(i,j)=DP(i-1,j) j<Ci
for(int i=1;i<=nums.size();++i){
for(int j=0;j<=target;++j){
//当前选择第i个物体,体积为nums[i-1],背包容量为j.
int Ci=nums[i-1];
if(j>=Ci){
dp[i][j]=dp[i-1][j]||dp[i-1][j-Ci]];
}else{
dp[i][j]=dp[i-1][j];
}
}
}
return dp[nums.size()][target];
}
优化为1维状态
bool canPartition(vector<int>& nums) {
if(nums.empty()) return false;
int sum=0;
for(auto i:nums)sum+=i;
if(sum%2==1) return false;
int pack=sum/2;//背包容量为和的一半
//01背包问题:状态方程