动态规划——背包问题

倖福魔咒の 提交于 2020-02-28 15:17:10

在写博客之前,先列出两个背包九讲链接以供参考
yxc在B站上讲的背包九讲:背包九讲专题
以及一篇非常经典的dd大牛的《背包九讲》

  • 0 1背包问题
  • 完全背包问题
  • 多重背包问题
  • 分组背包问题

0 1背包问题

有N件物品和一个容量为V的背包。第i件物品的费用是c[i](本题里是v[i]),价值是w[i]。

这是最基础的背包问题,特点是:每种物品仅有一件,可以选择放或不放。

在求状态转移方程的方面,思路是集合划分:

在这里插入图片描述
f(i,j):“在i个东西里面选质量为j的东西的总商品价值”这一集合可分为“选的所有东西里面不包含i”和“选的东西里面包含i”两种情况。
的话

不含i的情况即是f(i-1,j)【在i-1个东西中选质量为j的东西】
含i的情况即是f(i-1,j-Vi)+wi【由于选了i,那就是在i-1个东西中选j-Vi质量的总价值加上i的价值】
将上述两个集合取价值最大的取法,即为“在i个东西里面选质量为j的东西的总商品价值”

优化1:减少操作次数
由于上述的情况中,含i的情况是不一定存在的(那是当背包最大重量j<Vi即装不下的时候不需要考虑)

优化2:将二维数组优化为滚动数组

滚动数组:如果f(i)的计算仅仅只用了f(i-1)来完成计算的话,就只需要开一个f[2][N]的数组即可(可以运用e=1-e来实现滚动)

值得注意的是,由于本题中可以通过容量这一列从最大容量到最小容量的遍历方法实现含i的情况在不含i的情况之后被更新(如果从最小容量到最大容量遍历的话,j-Vi是恒小于j的,在更新到j-Vi时仍然是i-1的数据),故这题可以只开一维数组实现

DP部分代码如下:

	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]);//v、w数组分别是体积和价值数组

完全背包问题

有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的费用是c[i],价值是w[i]。

这个问题非常类似于01背包问题,所不同的是每种物品有无限件。也就是从每种物品的角度考虑,与它相关的策略已并非取或不取两种,而是有取0件、取1件、取2件……等很多种。

朴素算法:
由于有每种物品都有无限件可以取用,所以还需要枚举每种物品选了多少件(枚举件数不能超过背包容量)

	for(int i=1;i<=n;i++)
		for(int j=0;j<=m;j++)
			for(int k=0;k*v[i]<=j;k++)
				f[i][j]=max(f[i][j],f[i-1][j-v[i]*k]+w[i]*k);

优化1:通过展开化简式子

分别将f[i,j]和f[i,j-v]展开,可以发现f[i,j]计算式可被化简为f[i,j]=max(f[i-1,j],f[i,j-v]+w[i]
在这里插入图片描述
优化2:将二维数组优化为滚动数组
这个优化和0 1背包在储存方式上的优化基本一致,分析如下:

0 1背包和完全背包问题的状态转移区别如下图
在这里插入图片描述
0 1背包优化为一维数组是要倒过来遍历的原因是因为j-Vi是恒小于j的,如果从最小容量到最大容量遍历的话,j-Vi是恒小于j的,在更新到j-Vi时变成了i的数据。这符合状态转移方程的要求。

DP部分代码如下:(与0 1背包问题非常相似)只是背包容量的遍历顺序不同

	for (int i = 1; i <= n; i ++ )
 	   for (int j = v[i]; j <= m; j -- )	//容量从大到小遍历
   	     f[j] = max(f[j], f[j - v[i]] + w[i]);//v、w数组分别是体积和价值数组

多重背包问题

有N种物品和一个容量为V的背包。第i种物品最多有n[i]件可用,每件费用是c[i],价值是w[i]。

这题目和完全背包问题很类似,特点是:每种物品都有自己特异的件数、花费、价值。

朴素算法:
每种物品有n[i]件可以取用,需要枚举每种物品选了多少件(枚举件数不能超过背包容量)

for(int i=1;i<=n;i++)
	for(int j=0;j<=m;j++)
		for(int k=0;k<=s[i]&&k*v[i]<=j;k++)	//与完全背包相比仅仅是加了一个判断条件而已
			f[i][j]=max(f[i][j],f[i-1][j-v[i]*k]+w[i]*k);

朴素算法的时间效率很低,通过下面的二进制优化可以把遍历每种物品的n[i]件物品的时间复杂度从O(n)优化为O(log n)
优化:通过展开化简式子不可行,换用二进制优化方式

详见多重背包问题的二进制优化


分组背包问题

有N件物品和一个容量为V的背包。第i件物品的费用是c[i],价值是w[i]。这些物品被划分为若干组,每组中的物品互相冲突,最多选一件(每组的数量)。

本问题在状态转移方面就只需要遍历每一组中的元素,存在不选,选第一个,选第二个……多种情况

状态转移分析过程如下:

对每一种取法进行遍历
在这里插入图片描述
dp部分代码为:

(分组背包问题的费用(体积)和价值都是二维存放)
    for (int i = 1; i <= n; i ++ )
        for (int j = m; j >= 0; j -- )
            for (int k = 0; k < s[i]; k ++ )	//遍历每一种取法
                if (v[i][k] <= j)
                    f[j] = max(f[j], f[j - v[i][k]] + w[i][k]);
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!