目录
前言
acwing-2 01背包问题
思路
思路误区(可跳)
代码
嗯,,在网上搜了一下蓝桥杯动态规划好像出题比较多,也没有任何其他建议,现在进度可能比较慢,所以就想着先学动态规划再看数论吧,不知道对不对🥀
前言
反正受到外界干扰够多了,看着"动态规划""背包"这些标题都觉得发怵。。。好好学吧
动态规划(Dynamic Programming, DP)是一种解决优化问题的算法思想,常用于求解最优化问题,我们这里主要学习的就是解决背包问题。(关于为什么叫动态规划,就只是个命名而已不用纠结)
背包问题就是给定一个容量为V,给出N件物品,每件物品有其体积和价值,让我们得出一种解决方案使得在满足背包容量V的前提下,这些选出的物品的总价值能达到最大。
而背包问题又按可选择的物品的次数分为:01背包(即每件物品最多只能被选择一次)、完全背包(即每件物品可以被选择无限次)、多重背包(即每种物品有限制的放入次数)、分组背包(即物品被分为若干组,每组中的物品最多只能选择一个放入背包)。
acwing-2 01背包问题
思路
(静下心认真看题目)
我的思考历程:
刚看到这道题的时候,我的第一印象是觉得变量好多,需要在纸上列一下,理清思路。认真读完题之后,我觉得这道题要求的就是我们在满足容量限制的条件下,选择一些(没有限制选多少件物品)物品使得我们所得到的价值最大。
那么顺着思路下去,也就是我们要列举所有的情况,以得到最大价值。
那么如何列举所有情况呢?好像是意识到要利用二维数组,不过行列分别表示什么含义没想出来。(考虑不够深入吧)
看完讲解之后:
我们的思路是没问题的,确实要利用二维数组来列举所有的情况,dp[i][j]数组,下标 :i表示只考虑前i件物品,j表示最大容量不超过j,dp数组的元素的含义是:在 i j 两个因素的限制下,所得到的最大价值。(注意区分数组元素的含义和数组下标含义的不同)
这里我们运用动态规划的算法思想,将问题分解为更小的子问题,这道题里子问题也就是考虑更少的物品和更小的背包容量。当我们说“从前i个物品中选”,我们实际上是在说:“如果我们只考虑前i个物品,那么我们可以得到什么结果?”
为什么这里子问题是这样的呢?(我之前误解 i 的含义是从这些可选择的物品中选 i 件物品,)
在背包问题中,我们的目标是在给定背包容量的限制下,选择一些物品以使得总价值最大。这个问题可能看起来很复杂,因为有很多物品可以选择,而且每个物品都有自己的重量和价值。会有非常多情况。但是当我们举一些简单的例子,观察一下。
如果只有一个物品可以选择,那么我们应该如何选择?这个问题的答案很简单:如果这个物品的重量小于或等于背包的容量,那么我们就选择这个物品;否则,我们就不选择任何物品。
然后,我们可以考虑一个稍微复杂一点的问题:如果有两个物品可以选择,那么我们应该如何选择?这个问题的答案也相对简单:我们可以分别考虑选择和不选择每个物品的情况,然后选择总价值最大的那一种。这里的思路是:每件物品我们都可以选择拿或者不拿。在有两个物品可以选择的情况下:
图中已经将我们只有一件物品可选时所得出的结果对于:当有两件物品可选时所发挥的作用用不同颜色标注出来。我们可以清晰的看到子问题对于当前问题的作用。
也就是说我们将状态计算(价值计算)分成两种情况:不包含第i个物品 dp[i-1][j] 和 包含第i个物品 dp[ i-1][ j-vi ]+wi
(这两个j表示的都是“只能在前i个物品中选,且容量不大于j” 的情况下的 j )
-
是因为我们把这个大的问题看成了,每次在之前的基础上多加一件可选择的物品,针对这个添加的物品,对原本已经有的结果进行选择添加这件新物品与否的情况判断,最终得到最大值。
到这里应该对动态规划解决背包问题的思路可以清楚了。(呼气)
下面是y总总结的图,上面思路明白的话,这里的一些名词应该也很容易能理解啦。
思路误区(可跳)
在学习动态规划的思路之前,我想过这种思路:
i 的含义是从这些可选择的物品中选 i 件物品,每一个i都计算出符合条件的最大价值,这样在下一次i的数量每次加一的时候,我们只需要在没有选择过的物品中进行挑选符合容量限制且使得价值最大的物品,以此来求得最终问题的答案。
好吧,我继续问:如果仅仅是看这种思路,而不考虑已有的动态规划思想的知识储备的情况下,这种思路是否可行呢?
取自bing:
这种思路在某些特定情况下可能是可行的,但并不适用于所有情况。
这种思路的一个主要问题是,它假设每次选择物品都是独立的,即选择某个物品不会影响到其他物品的选择。然而,在许多实际问题中,物品之间往往是有关联的。例如,选择了某个物品可能会限制我们选择其他物品的能力。在这种情况下,我们不能简单地从剩余的物品中挑选一个符合容量限制且使得价值最大的物品。(关于这种情况我又继续问了举出一个例子:如下图)
这种方法可能并不总是能得到最优解,原因在于它没有充分利用动态规划的优势——记忆化搜索和避免重复计算。在动态规划中,我们会记录下在每个状态(即考虑了前i个物品,背包容量为j时的最大价值)下的最优解,然后在这个基础上考虑下一个物品。这样可以避免在考虑每个物品时都要从头开始计算,大大提高了效率。
好啦,思路就说到这里啦。
代码
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1010;
int n,m;//n表示物品数量 m表示背包容量
int v[N],w[N];//v数组存储每件物品的体积,w数组存储每件物品的权值
int dp[N][N];//dp[i][j]二维数组的每个元素表示的是:只从前i个物品中选择,且容量要小于j的情况下的最大价值
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>v[i]>>w[i];//读取每件物品
//当i为0,也就是有0个可选物品,那么其对应的所有j容量值的情况下价值都为0,刚好初始化的时候就是全部初始化为0,因此我们i从1开始
for(int i=1;i<=n;i++)
{
for(int j=0;j<=m;j++)
{
//我们双层循环就是为了进行状态计算,给dp数组赋值
//不包含第i个物品
dp[i][j]=dp[i-1][j];
//包含第i个物品 只有当第i个物品的体积小于当前最大容量的时候我们才考虑这种情况,否则没有意义
if(j>=v[i])dp[i][j]=max(dp[i][j],dp[i-1][j-v[i]]+w[i]);
}
}
cout<<dp[n][m];
return 0;
}
一些必要的解释就放在注释里啦。
只要我们清楚dp数组及其下标的含义,对应到代码中每一步的作用也就很清晰了。
先写到这里啦。这两天会补一下优化成一维数组的写法。(宿舍要关门了😂)
动态规划的学习开始的很艰难,不过相信会越来越清晰的!
如果有问题欢迎指出,非常感谢!!一起加油!!!