01背包:有n件物品(每件都只有一个)和一个体积为V的背包。第i件物品的价值为val[i],体积为m[i]。现在需要在不超过背包容量的情况下,使得背包里的东西价值最大?
之所以叫这类问题为01背包,是因为每件物品都只有一个,那么就只有两种选择,放or不放。
可以定义二位数字dp[i][j] 表示在一个容量为j 的背包中放前i种物品得到的最大价值。(问题的状态)
由此可推得状态转移方程 dp[i][j]=max(dp[i-1][j],dp[i-1][j-m[i]]+val[i])。(状态转移方程)
---------------------------------------------------------------------------------------------------------理解---------------------------------------------------------------------------------------------------------------------
考虑第i个物品取还是不取。
若第i个物品不取,则dp[i][j]=dp[i-1][j]表示价值没有变。
若第i个物品要取,则dp[i][j]=dp[i-1][j-m[i]]+val[i] 表示在容量为j的背包,m[i]的部分放物品i,剩下j-m[i]的空间放i-1个物品。(若i的大小超过j,也是不取。)
然后比较2种情况的价值大小,取大的。
---------------------------------------------------------------------------------------------------------优化---------------------------------------------------------------------------------------------------------------------
考虑在更新第i行时只会利用第i-1行,而i-1行前面的都已经没有用了并且在后面的更新中也不会用到。所以可以用一维数组优化。
dp[j] 的意义和之前的一样,但循环时仍需i从1->n循环,而j从V->m[i]循环。原因是更新时需要前面的数据,所以需要从后面更新,而且当背包容量小于第i个物品的大小时,就可以不用优化了。
核心代码
//N为物品数,V为背包总容量,第i个物品的大小为w[i],价值为v[i]
//二维数组
for(int i=1;i<=N;i++)
for(int j=0;j<=V;j++)
if(j>=w[i]) dp[i][j]=max(dp[i-1][j-w[i]]+v[i],dp[i-1][j]);
else dp[i][j]=dp[i-1][j];
//一维数组优化
for(int i=1;i<=N;i++)
for(int j=V;j>=w[i];j--)
dp[j]=max(dp[j-w[i]]+v[i],dp[j]);
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
状态和01背包的状态相同,也用dp[i][j] 表示在一个容量为j 的背包中放前i种物品(每种物品可放多个)得到的最大价值。(问题状态)
dp[i][j]=max(dp[i-1][j],dp[i-1][j-k*m[i]]+k*val[i]) 0<k*m[i]<=j (状态转移方程)
---------------------------------------------------------------------------------------------------------理解---------------------------------------------------------------------------------------------------------------------
与01背包相似,不同之处在于对于每一种物品枚举放多个。
在两层循环内继续套一层循环为 k 0->(k*m[i]<=j)为止
---------------------------------------------------------------------------------------------------------优化---------------------------------------------------------------------------------------------------------------------
同样也是一维数组做优化,还有其他优化比如将一种物品拆成多种物品,利用二进制表示。下面为一位数组的优化,因为它的复杂度最低。
在选第i种物品时,背包可能已经选入了第i种物品。所以需要的是dp[i][j-m[i]]而不是dp[i-1][j-m[i]]。
代码如下:
//n为物品数,V为背包总容量,第i个物品的大小为m[i],价值为val[i]
//二维数组
for(int i=1;i<=n;i++)
for(int j=0;j<=V;j++)
for(int k=1;k*m[i]<=j;k++)
dp[i][j]=max(dp[i-1][j],dp[i-1][j-k*m[i]]+k*val[i]);
//一维优化
for(int i=1;i<=n;i++)
for(int j=m[i];j<=V;j++)
dp[j]=max(dp[j],dp[j-m[i]]+val[i]);
多重背包:同样有n件物品和一个体积为V的背包。第i件物品的价值为val[i],体积为m[i],并且有n[i]个。现在需要在不超过背包容量的情况下,使得背包里的东西价值最大?
跟完全背包的思路基本一样,状态仍是dp[i][j] 表示在一个容量为j 的背包中放前i种物品(每种物品可放多个)得到的最大价值。(问题状态)
dp[i][j]=max(dp[i-1][j],dp[i-1][j-k*m[i]]+k*val[i]) 0<k<=n[i] || k*m[i]<=j (状态转移方程)
---------------------------------------------------------------------------------------------------------理解---------------------------------------------------------------------------------------------------------------------
对于每种物品,选取的个数有了限制,其余的没多大变化,也就是说最内层的循环结束的限制条件发生了改变。
---------------------------------------------------------------------------------------------------------优化---------------------------------------------------------------------------------------------------------------------
也可以将一种物品拆成多种物品,比如说一种物品有13个,因为知道每个数字都可以有二进制表示,那么这种物品就可以拆为(1*m[i],1*val[i]),(2*m[i],2*val[i]),(4*m[i],4*val[i]),(6*m[i],6*val[i])这四种物品。然后即可转化为01背包求解。
伪代码如下:
枚举每一个物品,对每一个物品i调用函数multiplepack(m[i],val[i],n[i])。
void completepack(int weight,int value){
for(int i=weight;i<=V;i++)
dp[i]=max(dp[i],dp[i-weight]+value);
}
void zeroonepack(int weight,int value){
for(int i=V;i>=weight;i--)
dp[i]=max(dp[i],dp[i-weight]+value);
}
void multiplepack(int weight,int value,int num){
if(weight*num>=V){
completepack(weight,value);
return ;
}
int k=1;
while(k<num){
zeroonepack(k*weight,k*value);
num-=k;
k*=2;
}
zeroonepack(num*weight,num*value);
}
另外一种优化利用到了优先队列。