dp-背包问题
背包问题理论模板
一个背包总容量为V, 现在有N个物品, 第i个物品容量为weight[i], 价值为value[i], 现在往背包里面装东西, 怎样装才能使背包内物品总价值最大.主要分为3类:
- 01背包, 每个物品只能取0个,或者1个.
- 完全背包, 每个物品可以取无限次.
- 多重背包, 每种物品都有个数限制, 第i个物品最多可以为num[i]个.
总体的,又分为背包刚好装满,与背包没有装满两种情况
01背包问题
每种物品都只有1个,只有选择与不选择两种状态
有N件物品和一个容量为V的背包,每种物品只有一件,可以选择放或不放。第i件物品的重量是w[i],价值是v[i]。求解将哪些物品装入背包可使这些物品的重量总和不超过背包容量,且价值总和最大。
对于任何只存在两种状态的问题都可以转化为01背包问题
定义状态dp[i][v]表示前i件物品恰放入一个容量为v的背包可以获得的最大价值。
状态转移方程:
dp[i][v]=max(dp[i-1][v], dp[i-1][v-w[i]]+v[i])
`dp[v]=max(dp[v],dp[v-w[i]]+v[i])`
空间复杂度O(NW)或者O(W),时间复杂度为O(nW)
for(int i=0;i<n;i++){
for(int j=背包大小;j>=w[i];j--){
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
}
}
分析:
首先,大循环从0到n是指n个物体
其次,内循环是从背包大小到当前物体的重量,是为了减少时间复杂度而这么弄的
对于w[i]是物体的重量,如果剩余背包的大小小于w[i]了,就无需再循环,所以一个端点在w[i]
而j的含义是当前的背包总容量,而容量不可能大于背包大小,所以另一个大小就在背包大小
如果是升序,那么就会对dp[j-w[i]]进行操作,也就意味着,如果j是w[i]的倍数,那么v[i]就会不断地加进去,也就是完全背包问题了
而对于降序的话,也就保证了每次第j个物品都只能被放入1此,而不是多次,就是01背包了
多重背包问题
每种物品最多有n件可用
有N种物品和一个容量为V的背包。第i种物品最多有n件可用,每件体积是c,价值是w。求解将哪些物品装入背包可使这些物品的体积总和不超过背包容量,且价值总和最大。
对于多重背包问题,可以转化为01背包问题,比如2个价值为5,重量为2的物品,可以转化为a和b两个物品,一个价值为5,重量为2,一个价值也为5,重量也为2
状态转移方程:
dp[i][j]=max(dp[i-1][j],dp[i-1][j-k*w[i]]+k*v[i]) 其中0<=k<=c[i]
`dp[j]=max(dp[j],dp[j-k*w[i]]+k*v[i])`
k是每种物品放的数量
for(int i=1;i<=n;i++)
for(int j=m;j>=0;j--)
for(int k=1;k<=c[i];k++){
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
}
}
}
完全背包问题
每种物品都有无限件
有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的体积是c,价值是w。将哪些物品装入背包可使这些物品的体积总和不超过背包容量,且价值总和最大。
状态转移方程:
dp[i][j]=max(dp[i][j],dp[i-1][v-k*w[i]]+k*v[i]) 其中0<=k*w[i]<=背包大小
`dp[j] = max(dp[j], dp[j - w[i]] + v[i]);`
分析见01背包物体,和01背包一样,就是内循环的循环方向不同而已,一个升,一个降
空间杂度为O(NW)或O(W),时间复杂度为O(NW)
代码
for (int i = 1; i <= n; i++) {
for (int j = w[i]; j <= 背包大小; j++) {
dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
}
}
背包混用
将3种背包进行混用,如果对于有些物品最多只能选一次,有些可以无限选
则利用01背包和完全背包的一行代码不同,进行判断
for(int i=0;i<n;i++){
if(第i个物品是01背包){
for(int j=背包大小;j>=w[i];j--){
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
}
}else if(第i个物品是完全背包){
for (int j = w[i]; j <= 背包大小; j++) {
dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
}
}
}
如果再加上多重背包的话
背包混用伪代码
for i= 1 to n
if 第i件物品属于01背包
ZeroOnePack(dp,wi,vi)
else if 第i件物品属于完全背包
CompletePack(dp,wi,vi)
else if 第i件物品属于多重背包
MultiplePack(dp,wi,vi,ni)
总结模板
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
const int N=1000000;
int v[N],w[N];
int dp[N];
void ZeroOnePack(int i){
for(int j=背包大小;j>=w[i];j--){
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
}
}
void CompletePack(int i){
for(int j=w[i];j<=背包大小;j++){
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
}
}
void MultiplePack(int i){
for(int j=m;j>=0;j--)
for(int k=1;k<=c[i];k++){
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
}
}
}
int main(){
memste(dp,0,sizeof(dp));
for(int i=0;i<n;i++){
if(第i个物品是01背包问题){
ZeroOnePack(i);
}else if(第i个物品是完全背包问题){
CompletePack(i);
}else if(第i个物品是多重背包问题){
MultiplePack(i);
}
}