动规背包模板题总结

  • 最近在重刷动规题
  • 捋了两天总算找到思路了
  • 所以做个总结

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++)//容量不够w[i],只能不选
		f[i][j]=f[i-1][j];
}
//所求结果为f[n][m].
  • 是不是很简单?那么下面进行空间的优化,压至一维。
一维空间
  • 再看一遍上面的二维代码,发现有点浪费空间,因为每个状态只会由上一层推得,前面的不会再用。
  • 由此发现可以进行一个简单有效的优化,将空间压缩到一维,状态转移方程为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--)//另一个小优化:j循环到w[i]停止,保证一定有足够的空间
		f[j]=max(f[j],f[j-w[i]]+c[i]);
//所求结果为f[m].
  • 这样是不是节省了很多空间?下面进入另一种背包题——完全背包。

完全背包

分析

  • 完全背包与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//01背包或多重背包
		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[m]即为所求.

二维费用的背包

分析

  • 费用多了一维,那么状态自然也要加一维。
  • 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;//别忘了初始化f[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]);
		} 
//f[m][n]即为所求.

分组背包

分析

  • 多了一个每组最多添加一件物品的限制,那么就需要再多加一层循环。
  • 这样就一共是三层循环,第一层是每组,第二层是逆序的背包容量循环,第三层才是组内的物品。
  • 注意组内物品的循环必须在背包容量的循环之内,这样才能保证每组最多选一件物品。

代码实现

//a[k][0]:第k组内物品总数
//a[k][1]~a[k][a[k][0]]:第k组内各物品编号
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[v]即为所求.

背包问题的方案总数

分析

  • 这道看着好像突然就简单了
  • 那就直接上代码吧

代码实现

f[0]=1;//注意初始化
for(int i=1;i<=n;i++)
	for(int j=a[i];j<=m;j++)
		f[j]+=f[j-a[i]];
//f[m]即为所求.

完结撒花!!!

  • 我终于写完啦!!!
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值