- 最近在重刷动规题
- 捋了两天总算找到思路了
- 所以做个总结
01背包
分析
- 有N件物品,每件物品有且仅有一件,因此每个物品只有选与不选两种状态。
- 因此想到可以用一个数组存储前i件,最大价值为j时的状态,依次更新状态,最后f [n][m] 即为所求。
代码实现
- 提供最基本的二维空间以及优化后的一维空间的版本,具体如下:
二维空间
- 设f [i][j] 为前i件使用最大容量为j时的最大收益,则有
f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+c[i])
,即选与不选第i件的最大值。
for(int i=1;i<=n;i++){
for(int j=w[i];j<=m;j++)
f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+c[i]);
for(int j=1;j<w[i];j++)
f[i][j]=f[i-1][j];
}
一维空间
- 再看一遍上面的二维代码,发现有点浪费空间,因为每个状态只会由上一层推得,前面的不会再用。
- 由此发现可以进行一个简单有效的优化,将空间压缩到一维,状态转移方程为
f[j]=max(f[j],f[j-w[i]]+c[i])
. - 现在出现了一个小问题:如何确定f[j]是由上一层得到的呢?
- 很简单,第二层循环从m开始,也就是倒序循环,这时的f[j-w[i]]还停留在上一层的状态,也就是没有选过第i件,这样就可以保证i只能选一次。
for(int i=1;i<=n;i++)
for(int j=m;j>=w[i];j--)
f[j]=max(f[j],f[j-w[i]]+c[i]);
- 这样是不是节省了很多空间?下面进入另一种背包题——完全背包。
完全背包
分析
- 完全背包与01背包很相似,不同的是01背包只可以选一次,而完全背包每一件都可以选无数次。
- 下面也提供二维和一维两种方式。
代码实现
二维空间
- 完全背包的状态转移方程也和01背包的很相似,为
f[i][j]=max(f[i-1][j],f[i][j-w[i]]+c[i])
,由f[i][j-w[i]]
得来,就可以选择无数次。
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(w[i]<=j) f[i][j]=max(f[i-1][j],f[i][j-w[i]]+c[i]);
else f[i][j]=f[i-1][j];
一维空间
- 继续进行优化,优化后的状态转移方程:
f[j]=max(f[j],f[j-w[i]]+c[i])
. - 为了保证f[j-w[i]]是在第i层的状态,第二层循环采用顺序。
for(int i=1;i<=n;i++)
for(int j=w[i];j<=m;j++)
f[j]=max(f[j],f[j-w[i]]+c[i]);
多重背包
分析
- 这种类型比前两种多加了最大数量的条件,相较于完全背包更像是01背包,可以再加一层循环控制数量,依次计算。
- (还有一种方式,在白书上)
代码实现(一维空间)
- 第二层循环采用倒序,与01背包相同。
- 第三层循环控制的是当前物品的数量,注意循环的条件。
for(int i=1;i<=n;i++)
for(int j=m;j>=0;j--)
for(int k=1;k<=s[i]&&k*v[i]<=j;k++)
f[j]=max(f[j],f[j-k*v[i]]+w[i]*k);
混合背包
分析
- 乍一看可能有一点点难,实际上就是前三种背包的混合版本,可以把整个问题分解开,一个一个解决。
代码实现(一维空间)
- 完全背包的第二层循环是顺序,其他两种都是逆序,所以需要进行判断。
for(int i=1;i<=n;i++){
if(!p[i])
for(int j=w[i];j<=m;j++)
f[j]=max(f[j],f[j-w[i]]+c[i]);
else
for(int k=1;k<=p[i];k++)
for(int j=m;j>=w[i];j--)
f[j]=max(fj],f[j-w[i]]+c[i]);
}
二维费用的背包
分析
- 费用多了一维,那么状态自然也要加一维。
- 设
f[x][y]
表示第一种付出代价最小为x,第二种付出代价最小为y时的最小重量。 - 状态转移方程:
f[x][y]=min(f[x][y],f[j][k]+c[i])
. - 其中
x=min(m,j+a[i]),y=min(n,k+b[i])
. - 因为每种物品都只有一件,所以第二、三层循环是逆序。
代码实现
memset(f,0x3f,sizeof(f));
f[0][0]=0;
for(int i=1;i<=l;i++)
for(int j=m;j>=0;j--)
for(int k=n;k>=0;k--){
int x=min(m,j+a[i]),y=min(n,k+b[i]);
f[x][y]=min(f[x][y],f[j][k]+c[i]);
}
分组背包
分析
- 多了一个每组最多添加一件物品的限制,那么就需要再多加一层循环。
- 这样就一共是三层循环,第一层是每组,第二层是逆序的背包容量循环,第三层才是组内的物品。
- 注意组内物品的循环必须在背包容量的循环之内,这样才能保证每组最多选一件物品。
代码实现
for(int k=1;k<=t;k++)
for(int j=v;j>=0;j--)
for(int i=1;i<=a[k][0];i++)
if(j>=w[a[k][i]]){
int x=a[k][i];
f[j]=max(f[j],f[j-w[x]]+c[x]);
}
背包问题的方案总数
分析
代码实现
f[0]=1;
for(int i=1;i<=n;i++)
for(int j=a[i];j<=m;j++)
f[j]+=f[j-a[i]];
完结撒花!!!