01背包
现在有1个体积为mmax的背包和n种物品(每种物品只有1个)。每种物品的体积和价值分别是v[i]和w[i]。求这个背包最多可以装价值多少的物品。
这是最基础的背包问题,特点是:每种物品仅有一件,可以选择放或不放。
用子问题定义状态:设f[i][j]表示前i件物品恰放入一个容量为j的背包可以获得的最大价值。
我们依次遍历,到了第i件物品了
如果此时背包剩余体积已经放不下这件物品了,那么f[i][j]=f[i-1][j];
如果还能放的下这件物品我们就要考虑,到底是放入还是不放入这件物品会差生最大价值呢,我们需要做一个比较,也就是
f[i][j]=max(f[i-1][j],f[i-1][]j-v[i]+w[i]);
解释一下f[i-1][j]是不放入这件物品,f[i-1][]j-v[i]+w[i]是放入这件物品(这件物品的价值+前i-1件,体积减少了这件物品的背包中的价值)
模板如下
#define N 10010
int v[N],w[N]; //每件物品的体积和价值
int n,mmax; //物品种类数 以及 背包体积
int f[N][N]; //dp数组
for(int i=1;i<=n;i++) cin>>v[i]>>w[i]; //提倡从1开始输入
for(int i=1;i<=n;i++)
for(int j=0;j<=mmax;j++)
{
if(j<v[i]) f[i][j]=f[i-1][j];
else f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i]);
}
cout<<f[n][mmax];
可以实现空间优化,采用一维数组,模板如下
#define N 10010
int v[N],w[N]; //每件物品的体积和价值
int n,mmax; //物品种类数 以及 背包体积
int f[N]; //dp数组
for(int i=1;i<=n;i++) cin>>v[i]>>w[i]; //提倡从1开始输入
for(int i=1;i<=n;i++)
for(int j=mmax;j>=0;j--)
{
if(f[j]<=f[j-v[i]]+w[i] && j-v[i]>=0)
f[j]=max(f[j],f[j-v[i]+w[i]);
}
cout<<f[mmax];
完全背包
现在有1个体积为mmax的背包和n种物品(每种物品有无限个)。每种物品的体积和价值分别是v[i]和w[i]。求这个背包最多可以装价值多少的物品。
这是最基础的背包问题,特点是:每种物品有无限个,可以选择放叶可以选择不放,选择放的时候可以放多个。
用子问题定义状态:设f[i][j]表示前i件物品恰放入一个容量为j的背包可以获得的最大价值。
我们依次遍历,到了第i件物品了
f[i][j]=max(f[i-1][j-k*v[i]]+k*w[i]) 0<=k*v[i]<=j
模板如下
#define N 10010
int v[N],w[N]; //每件物品的体积和价值
int n,mmax; //物品种类数 以及 背包体积
int f[N][N]; //dp数组
for(int i=1;i<=n;i++) cin>>v[i]>>w[i]; //提倡从1开始输入
for(int i=1;i<=n;i++)
for(int j=0;j<=mmax;j++)
for(int k=0;k*v[i]<=j;k++)
{
f[i][j]=max(f[i-1][j-k*v[i]]+k*w[i])
}
cout<<f[n][mmax];
通常我们不用上述模板吗,而是采用优化后的模板,如下
优化思路: 转化为01背包问题求解
既然01背包问题是最基本的背包问题,那么我们可以考虑把完全背包问题转化为01背包问题来解。最简单的想法是,考虑到第i种物品最多选mmax/v[i]件,于是可以把第i种物品转化为mmax/v[i]件体积及价值均不变的物品,然后求解这个01背包问题。这样完全没有改进基本思路的时间复杂度,但这毕竟给了我们将完全背包问题转化为01背包问题的思路:将一种物品拆成多件物品。
更高效的转化方法是:把第i种物品拆成体积为v[i]*2^k、价值为w[i]*2^k的若干件物品,其中k满足v[i]*2^k<=mmax。这是二进制的思想,因为不管最优策略选几件第i种物品,总可以表示成若干个2^k件物品的和。这样把每种物品拆成O(log(mmax/v[i]))件物品,是一个很大的改进。
代码:
#define N 10010
int v[N],w[N]; //每件物品的体积和价值
int n,mmax; //物品种类数 以及 背包体积
int f[N]; //dp数组
for(int i=1;i<=n;i++) cin>>v[i]>>w[i]; //提倡从1开始输入
for(int i=1;i<=n;i++)
for(int j=v[i];j<=mmax;j++)
f[j]=max(f[j],f[j-v[i]]+w[i]);
cout<<f[mmax];
多重背包
有N种物品和一个容量为V的背包。第i种物品最多有n[i]件可用,每件费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
这题目和完全背包问题很类似。基本的方程只需将完全背包问题的方程略微一改即可,因为对于第i种物品有n[i]+1种策略:取0件,取1件……取n[i]件。
状态转移方程:f[i][j]=max{f[i-1][j-k*v[i]]+k*w[i]} 0<=k<=n[i]
模板如下
#define N 10010
int v[N],w[N],num[N]; //每件物品的体积和价值以及数量
int n,mmax; //物品种类数 以及 背包体积
int f[N]; //dp数组
for(int i=1;i<=n;i++) cin>>v[i] >>w[i]>>num[i];
for(int i=1;i<=n;i++)
for(int j=mmax;j>=0;j--)
for(int k=0;k<=n[i];k++)
{
if(j-k*v[i]<0) break;
f[j]=max(f[j],f[j-k*v[i]]+k*w[i]);
}
cout<<f[mmax];
优化后模板
#define N 10010
int v[N],w[N],num[N]; //每件物品的体积和价值以及数量
int n,mmax; //物品种类数 以及 背包体积
int f[N]; //dp数组
for(int i=1;i<=n;i++) cin>>v[i] >>w[i]>>num[i];
int k = n + 1;
for(int i=1;i<=n;i++)
{
while(num[i]!=1)
{
v[k]=v[i];
w[k]=w[i];
k++;
num[i]--;
}
}
for(int i=1;i<=k;i++)
for(int j=m; j>=1;j--)
if (v[i]<=j) f[j]=max(f[j],f[j-v[i]]+w[i]);
cout<<f[mmax];
部分参考自https://blog.csdn.net/stack_queue/article/details/53544109