1、问题描述:
2、解决思路:
一般动态规划问题难就难在思路难以理解,一旦思路理解了代码非常好写,一般的动态规划题目我们可以分成两部分思考,一是问题的每一种状态如何表示,另一部分是如何从一个状态转移到另一个状态,也就是列出状态转移方程。
当第i种物品选0个也就是一个都不选的时候,其实就是与前i-1种物品中选,体积不大于j的最大价值等价,
当第i种物品选1个的时候,可以看作是从前i-1种物品中选,体积不大于j-v[i]的最大价值加上1个第i种物品的价值,
……
当第i种物品选s[i]个的时候,可以看作是从前i-1种物品中选,体积不大于j-s[i]*v[i]的最大价值加上s[i]个第i种物品的价值
所以状态转移方程:
3、朴素动态规划代码:
此代码利用了滚动数组来进行存储空间压缩
// //1、朴素动态规划做法
#include<iostream>
#include<algorithm>
using namespace std;
const int N=110;
/*f[i][j]表示从前i种物品选,最大体积不超过j的物品最大价值
v[i]表示第i种物品的体积,w[i]表示第i种物品的价值
s[i]表示第i种物品最多能选几个*/
int f[N][N],n,m,v[N],w[N],s[N];
int main()
{
scanf("%d%d",&n,&m);//输入物品的种数n,背包的最大体积m
for(int i=1;i<=n;i++)
scanf("%d%d%d",&v[i],&w[i],&s[i]);
for(int i=1;i<=n;i++)//遍历前i种物品
for(int j=0;j<=m;j++)//体积从0开始遍历
for(int k=0;k<=s[i]&&k*v[i]<=j;k++)//第i种物品选择k个
f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);
printf("%d\n",f[n][m]);//f[n][m]表示从前n种物品里面选,体积不大于m的最大价值
return 0;
}
4、二进制优化做法
有没有办法可以对时间复杂度进行优化呢?我们可以发现朴素动态规划用了三重循环来遍历每一个选法,其中第三层循环遍历了每个可以选择的第i种物品,我们可以把第i种物品采用类似二进制数字的表示方法将s[i]个物品划分S[i]=1+2+4+……+2k+s,每一组可以看成01背包问题的一个物品,注意:和实际的二进制不同的是,最后一位的权值是s。这样就可以将s[i]个物品划分成log2(s)(向上取整)个新物品,然后转化成01背包问题来解决,所以我们可以将最后一层循环的时间复杂度从S优化成log(S)
5、二进制优化代码:
//2、二进制优化法
#include<iostream>
#include<algorithm>
using namespace std;
const int M=15000;//O(nlog(s)),多开一点防止越界
/*f[j]表示选择的物品体积不大于j时的最大价值,v[i]表示一组物品的总体积,w[i]表示一组物品的总价值*/
int n,m,f[M],v[M],w[M];
int main()
{
int cnt=0;//cnt记录物品分组的组数
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
int a,b,s;
scanf("%d%d%d",&a,&b,&s);//输入第i种物品的体积、价值、数量
int k=1;//统计第i种物品
while(k<=s)
{
cnt++;
v[cnt]=a * k;
w[cnt]=b * k;
s-=k;
k*=2;
}
if(s>0)
{
cnt++;
v[cnt]=a * s;
w[cnt]=b * s;
}
}
n=cnt;//将问题转换为01背包后,1组物品就变成了01背包里的1个物品
//求01背包的动态规划算法
for(int i=1;i<=n;i++)
for(int j=m;j>=v[i];j--)
f[j]=max(f[j],f[j-v[i]]+w[i]);
printf("%d\n",f[m]);//输出选择物品体积不大于m的最大价值
return 0;
}
6、时、空复杂度分析
(1)二进制优化时间复杂度:
(2)二进制优化空间复杂度:(利用了滚动数组)