动态规划——0-1背包,完全背包,多重背包及优化问题

一,01背包
问题描述:给定n个物体(它们的重量为:w1,w2,…,wn,价值为:v1,v2,…,vn) 和 一个承受重量为W的背包,问怎么选取这些物体,放在背包中(不超过背包的承重),让所取的子集达到最大价值。
状态转移方程: dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i];
dp[i][j]表示前i个物品在背包容量为j的情况下的最大价值。

#include<iostream>
#include<cstdio>
using namespace std;
int n,m;
int dp[30][40000];
int w[30],v[30];
int main(){
 cin>>n>>m;
 for(int i=1;i<=m;i++)cin>>w[i]>>v[i];
 for(int i=1;i<30;i++)dp[i][0]=0;
 for(int j=1;j<30000;j++)dp[0][j]=0;
 for(int i=1;i<=m;i++){
  for(int j=1;j<=n;j++){
   if(w[i]<=j)
   dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
   else dp[i][j]=dp[i-1][j];
  }
 } 
 cout<<dp[m][n]<<endl;
 return 0;
} 

在这里有个小插曲。如果还需要知道最后拿了哪几个物品?
+回溯:

int nn=n;
  for(int i=m;i>1;i--){
   if(dp[i][nn]==dp[i-1][nn])vis[i]=0;
   else{
    vis[i]=1;
    nn-=a[i];
   }
  }
  vis[1]=dp[1][nn]>0?1:0;

vis[I]=1示拿了,否则表示未拿。
一维数组实现
为什么可以用一维数组实现?因为第i层的数据只会从第i-1层转化出来。所以我们最基本的思维是用一个两行的二维数组轮流交替(滚动数组)。但是我们也可以只用一个一维数组,因为第i层的数据由第i-1层更左边的数据求出,所以此时必须需要将第二层for循环倒序遍历即可。

#include<iostream>
#include<cstdio>
using namespace std;
int n,m;
int dp[40000];
int v[30],w[30];
int main(){
 cin>>n>>m;
 for(int i=1;i<=m;i++)cin>>w[i]>>v[i];
 for(int i=1;i<=m;i++){
  for(int j=n;j;j--){
   if(j>=w[i])dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
  }
 }
 cout<<dp[n]<<endl;
 return 0;
} 

二,完全背包
问题描述:完全背包是在01背包的基础上加了个条件——这n种物品都有无限的数量可以取,问怎样拿才可以实现价值最大化。
虽然题意中每种有无限件,但这里有个隐藏条件:背包承重量的固定性导致每种最多只能取某个值,再多就放不下了,这个值就是W / wi。也就是说,对于第 i 种物品,它可以取0,1,2,…,W / wi(向下取整)件。而在01背包中,对于第 i 种物品,只能取0,1件。我们可以看到,01背包其实就是完全背包的一个特例。所以我们可以用类似01背包的思路写出完全背包的基本算法。
状态转移方程:dp[i][j]=max(dp[i-1][j-kw[i]+kv[i]],0<=k<=j/w[I];

#include<iostream>
#include<cstdio>
using namespace std;
int n,m;
int dp[30][40000];
int w[30],v[30];
int main(){
 cin>>n>>m;
 for(int i=1;i<=m;i++)cin>>w[i]>>v[i];
 for(int i=1;i<30;i++)dp[i][0]=0;
 for(int j=1;j<30000;j++)dp[0][j]=0;
 for(int i=1;i<=m;i++){
  for(int j=1;j<=n;j++){
   for(int k=0;k*w[i]<=j;k++){
    dp[i][j]=max(dp[i][j],dp[i-1][j-k*w[i]]+k*v[i]);
   }
  }
 } 
 cout<<dp[m][n]<<endl;
 return 0;
} 

一维数组实现
完全背包的一维数组实现和01背包也是几乎完全相同,唯一差别是完全背包的内循环是正向遍历,而01背包的内循环是逆向遍历。

#include<iostream>
#include<cstdio>
using namespace std;
int n,m;
int dp[40000];
int w[30],v[30];
int main(){
 cin>>n>>m;
 for(int i=1;i<=m;i++)cin>>w[i]>>v[i];
 for(int i=1;i<=m;i++){
  for(int j=w[i];j<=n;j++){
   dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
  }
 }
 cout<<dp[n]<<endl;
 return 0;
} 

三,多重背包
问题描述:多重背包是在01背包的基础上,加了个条件:第 i 件物品有ni件。
多重背包问题的思路跟完全背包的思路非常类似,只是k的取值是有限制的,因为每件物品的数量是有限制的,状态转移方程为:
dp[i][j]=max(dp[i-1][j-kw[i]]+kv[i];0<=k<=min(x[i],j/w[i])

#include<iostream>
#include<cstdio>
using namespace std;
int n,m;
int dp[30][40000];
int w[30],v[30],x[30];
int main(){
 cin>>n>>m;
 for(int i=1;i<=m;i++)cin>>w[i]>>v[i]>>x[i];
 for(int i=1;i<30;i++)dp[i][0]=0;
 for(int j=1;j<30000;j++)dp[0][j]=0;
 for(int i=1;i<=m;i++){
  for(int j=1;j<=n;j++){
   for(int k=0;k*v[i]<=j&&k<=x[i];k++){
    dp[i][j]=max(dp[i][j],dp[i-1][j-k*w[i]]+k*v[i]);
   }
  }
 } 
 cout<<dp[m][n]<<endl;
 return 0;
} 

多重背包也有个优化问题,同样是优化第三维的for循环。每个背包有x[i],我们能不能减少背包的个数同样能够组合出x[i]。
二进制拆分:
在这里插入图片描述
在这里插入图片描述

拆分完成后,多重背包问题就完全转化成了0-1背包问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值