第一讲 01背包
题目
给定物品个数n,背包容量v,每个物品都有一个体积c和价值w,要求向背包中装物品使得总价值最高.
基本思路
状态表示:f(i,j)表示前i个物品试图装入一个容量为j的背包的最大价值.
边界情况:f(0,j)=0.
状态转移:f(i,j)=max(f(i-1,j),f(i,j-save[i])+value[i]). 即装或不装第i个物品
时间复杂度O(VN) 空间复杂度O(VN)
例1 hdu2602 01背包裸题
两个wa点,第一个是多组数据没有注意到,第二个是存在体积为0的情况,所以j需要从0枚举起.
memset(dp,0,sizeof(dp));
for(int i=1;i<=n;i++) scanf("%d",&value[i]);
for(int i=1;i<=n;i++) scanf("%d",&volume[i]);
for(int i=1;i<=n;i++)
for(int j=0;j<=v;j++)
if(j<volume[i])
dp[i][j]=dp[i-1][j];
else
dp[i][j]=max(dp[i-1][j],dp[i-1][j-volume[i]]+value[i]);
printf("%d\n",dp[n][v] );
优化空间复杂度
注意到每个状态f(i,j)只与之前的状态f(i-1,j)和f(i-1,j-save[i])有关.实际上开一个二维数组是不必要的,完全可以开一个一维数组下标为j,然后循环递推n次.
即f(j)=max(f(j),f(j-save[i])+value[i]),但是正向循环是错误的,因为之前的j-save[i]已经被新状态所覆盖,正确的做法只要反转一下顺序,逆向循环就好.
int n=read(),v=read();
for(int i=1;i<=n;i++)value[i]=read();
for(int i=1;i<=n;i++)volume[i]=read();
memset(dp,0,sizeof(dp));
for(int i=1;i<=n;i++)
for(int j=v;j>=volume[i];j--)
dp[j]=max(dp[j],dp[j-volume[i]]+value[i]);
printf("%d\n",dp[v] );
注意此时这个数组的意义是:容量为j的背包最多能装多少价值的物品.
遍历i个物品后,得到结果.
初始化的细节问题(满包)
01背包满包问题:给定物品个数n,背包容量v,每个物品都有一个体积c和价值w,请问恰好装满背包时最大总价值是多少.
状态表示,转移方程同理:f(j)=max(f(j),f(j-save[i])+value[i]))
但是初始条件:f(j)=-∞,f(0)=0.意义是0件物品时不可能装到除0之外的容量,-∞表示无解.
递推结束后会有很多状态都是无解,表示不能达到.
例2 luogu P1164 小A点菜 01背包满包求方案数
状态表示:f(j)表示容量为j的背包装满有几种方案.
初始条件:f(j)=0,f(0)=1
状态转移:f(j)=f(j)+f(j-save[i])
int n=read(),m=read();
for(int i=1;i<=n;i++)
save[i]=read();
dp[0]=1;
for(int i=1;i<=n;i++)
for(int j=m;j>=0;j--)
dp[j]=dp[j]+dp[j-save[i]];
printf("%d\n",dp[m] );
一个常数优化
01背包二重循环的下限可以从Ci优化成max(V − Σ(i到n)C, Ci)
在这之前我有一个疑惑,为什么下限会是ci,这在未优化空间复杂度时是不成立的.
但是优化空间复杂度后显然成立,因为”什么都不做”就是dp[j]=dp[j].
所以转移方程就是
for(int i=1;i<=n;i++)
for(int j=v;j>=volume[i];j--)
dp[j]=max(dp[j],dp[j-volume[i]]+value[i]);
下面考虑原文中的优化,这个数值是仅与i有关的.
看了网上大佬的思路,意思是某些算出来的背包实际上对最终结果是没有帮助的.
最多有帮助的就是总体积减去之后每个物品的体积之和.
比如计算最后一件物品时,V-C(n-1)以下容量的背包是不可能转移到结果的.
同理,计算倒数第二件物品时,V-C(n-1)-C(n-2)以下的背包也是不可能转移到最终结果的.
int n=read(),v=read(),lim=v;
for(int i=1;i<=n;++i) value[i]=read();
for(int i=1;i<=n;++i) volume[i]=read(),lim-=volume[i];
memset(dp,0,sizeof(dp));
for(int i=1;i<=n;++i)
{
int th=max(lim,volume[i]);
for(int j=v;j>=th;--j)
dp[j]=max(dp[j],dp[j-volume[i]]+value[i]);
lim+=volume[i];
}
printf("%d\n",dp[v] );
练习题
见之后的训练题博客.