01背包问题:
有n件物品,每件物品的重量为w[i],价值为c[i]。现在有一个容量为V的背包,问如何选取物品放入背包,使得背包内
物品的总价值最大。其中每种物品都只有1件。样例:
5 8 //n=5 V=8 5件物品 8大的包
3 5 1 2 2 //w[i] 每件物品的重量 w[i]之和有上限
4 5 2 1 3 //c[i] 每件物品的价值 要求价值之和尽可能大 求最大取5+1+2=8 最大价值5+2+3=10
令:dp[i][v]表示前i件物品(1<=i<=n,0<=v<=V)恰好装入容量为v的背包中所能获得的最大价值
dp[i][v]={
dp[i-1][v] //不放第i件物品
dp[i-1][v-w[i]]+c[i] //放第i件物品,转化为前i-1件物品恰好装入容量为v-w[i]的背包中所能获得的最大价值
}
#include
#include
#include
using namespace std;
const int N=1000;
const int V=10000;
int w[N],c[N];//重量 价值
int dp[N][V];
int main(){
int n,V;
while(cin>>n>>V){
for(int i=1;i<=n;i++){
cin>>w[i]; //重量
}
for(int i=1;i<=n;i++){
cin>>c[i];//价值
}
memset(dp,0,sizeof(dp));
for(int i=1;i<=n;i++){
for(int v=1;v<=V;v++){
if(v
注意:状态转移时直接写:
for(int i=1;i<=n;i++){ for(int v=w[i];v<=V;v++){ dp[i][v]=max(dp[i-1][v],dp[i-1][v-w[i]]+c[i]); } }
是错误的,第二组数据即可测出来,后面的dp[i][v]需要前面的的dp[i-1][v-w[i]] 当w[i]变小了后(7<9且7<8) dp[i-1][v-w[i]]第一维v-w[i]会变大,从w[i]~V枚举 前面的0~w[i]会一直保持为0,一旦后面的w[i]变小了,产生了新的0~w[i]最小区间,就可能出错 即是逆序也不行 二维的还必须写if..else
很明显二维数组形式好理解,但是定义二维数组时总容量有多大,第二维就多大,浪费空间,很多时候根本开不了那么大的数组。于是有了下面第二种更常见的写法!
不需要记录所有之前的dp[i][v]状态 每次只需要知道上一次dp[i-1][v]的所有v取值状态即可 其实是dp[i-1][v]和dp[i-1][v-w[i]]两个状态。
for(int i=1;i<=n;i++){
for(int v=V;v>=w[i];v--){
dp[v]=max(dp[v],dp[v-w[i]]+c[i]);
}
}
式子中dp[v]=dp[v]=max(dp[v],dp[v-w[i]]+c[i]); max()内的dp[v]其实是上次的dp[v]即dp[i-1][v]。
必须逆序枚举是因为求dp[v]时可能需要上一次的dp[v-w[i]],如若正序枚举,那么上次的dp[v-w[i]]被提前更新覆盖了。
参考博文
#include
#include
#include
using namespace std;
const int N=1000;
const int V=10000;
int w[N],c[N];//重量 价值
int dp[V];
int main(){
int n,V;
while(cin>>n>>V){
for(int i=1;i<=n;i++){
cin>>w[i]; //重量
}
for(int i=1;i<=n;i++){
cin>>c[i];//价值
}
memset(dp,0,sizeof(dp));
for(int i=1;i<=n;i++){
for(int v=V;v>=w[i];v--){
dp[v]=max(dp[v],dp[v-w[i]]+c[i]);//每次等号前的dp[v]相当于dp[i][v] 等号后的dp[v]相当于dp[i-1][v]
}
}
cout<
有n种物品,每种物品的单件重量为w[i],价值为c[i].现在有一个容量为V的背包,问如何选取物品放入背包,使得背包内
物品的总价值最大。其中每种物品都有无穷件。
令:dp[i][v]={
dp[i-1][v] //不放第i件物品
dp[i][v-w[i]]+c[i] //只是是i不是i-1 每件物品可以放多次 转移到dp[i][v-w[i]]的状态
}
二维形式:
和01背包一样,仅仅把i-1改成i
for(int i=1;i<=n;i++){
for(int v=1;v<=V;v++){
if(vdp[i][v]=dp[i-1][v];//从小到大枚举 边界0也初始化了 前面的肯定都已经知道了
}else{
dp[i][v]=max(dp[i-1][v],dp[i][v-w[i]]+c[i]);
}
}
}一维形式: 和01背包一样,v的枚举顺序变了,
正序枚举 因为需要的是dp[i-1][v]和dp[i][v-w[i]], dp[i-1][v]没什么,每次都是max内的dp[v]都是dp[i-1][v]
但是正序枚举会在求dp[v]之前提前更新dp[i][v-w[i]], 此时需要的就是dp[i][v-w[i]]而非dp[i-1][v-w[i]]
for(int i=1;i<=n;i++){
for(int v=w[i];v<=V;v++){
dp[v]=max(dp[v],dp[v-w[i]]+c[i]);//每次等号前的dp[v]相当于dp[i][v] 等号后的dp[v]相当于dp[i-1][v]
}
}
#include
#include
#include
using namespace std;
const int N=1000;
const int V=10000;
int w[N],c[N];//重量 价值
int dp[V];
//完全背包
int main(){
int n,V;
while(cin>>n>>V){
for(int i=1;i<=n;i++){
cin>>w[i]; //重量
}
for(int i=1;i<=n;i++){
cin>>c[i];//价值
}
memset(dp,0,sizeof(dp));
for(int i=1;i<=n;i++){
for(int v=w[i];v<=V;v++){//正序
dp[v]=max(dp[v],dp[v-w[i]]+c[i]);//每次等号前的dp[v]相当于dp[i][v] ?等号后的dp[v]相当于dp[i-1][v]?
}
}
cout<