目录
在开始看完全背包问题之前,可能需要先了解01背包及其解决办法。 指路👇
【第二十五课】动态规划:01背包问题(acwing-2 / 思路 / 含一维数组优化 / c++代码)
思路
这个问题和01背包的区别就是每件物品可以选无限次(当然在背包容量限制之内)。
也就是说,01背包中我们在面对第i件物品的时候,有两种选择,分别是:选和不选,也就是第 i 件物品选择0次和1次,而完全背包也就是说,我们可选择的选项变多了,我们可以选择第i件物品 0,1,2,3,......k次,也就是在01背包分析问题方法的基础上,做了一点变化,如图
如果仅仅是这里发生了改变的话,其实我们在不考虑优化情况下,最朴素的写法应该是很容易得出的。
状态转移方程变为:dp[i][j]=max(dp[i-1][j],dp[i-1][j-k*v[i]]+k*w[i])
都是为了填充这个二维数组,只不过把选择从0和1两个变成了0-k个。
朴素代码
#include<iostream>
using namespace std;
const int N=1010;
int n,m;
int v[N],w[N];
int dp[N][N];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>v[i]>>w[i];
for(int i=1;i<=n;i++)
{
for(int j=0;j<=m;j++)
{
for(int k=0;k*v[i]<=j;k++)
{
dp[i][j]=max(dp[i][j],dp[i-1][j-k*v[i]]+k*w[i]);
}
}
}
cout<<dp[n][m];
return 0;
}
这种写法应该可以理解。
但是我们也看到了嵌套了三层循环。因此我们想着对他进行优化
优化
公式推导上
经过上面的分析我们知道完全与01背包问题的区别就是选择这件物品的次数,那么我们想试着用01背包的状态转移方程来写完全背包的,观察一下有没有什么规律,能够简化一层循环的。
下面图中是关于公式推导的过程
下面这个视频老师讲了具体的公式推导的过程,也对二维数组的填充过程给出了呈现,非常详细,推荐看一下帮助理解二维的写法。(可以倍速观看)
下面这个老师是按照公式推导变化的方式进行讲解的,也很清晰,推荐看一下。
【完全背包问题求解-NOIP/CSP/蓝桥杯算法试学课-青少年C++编程】
二维代码
#include<iostream>
using namespace std;
const int N=1010;
int n,m;
int v[N],w[N];
int dp[N][N];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>v[i]>>w[i];
for(int i=1;i<=n;i++)
{
for(int j=0;j<=m;j++)
{
dp[i][j]=dp[i-1][j];
if(j>=v[i])dp[i][j]=max(dp[i][j],dp[i][j-v[i]]+w[i]);
}
}
cout<<dp[n][m];
return 0;
}
一维代码
#include<iostream>
using namespace std;
const int N=1010;
int n,m;
int v[N],w[N];
int dp[N];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>v[i]>>w[i];
for(int i=1;i<=n;i++)
{
for(int j=v[i];j<=m;j++)
{
dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
}
}
cout<<dp[m];
return 0;
}
公式理解上
如果不看公式推导,单纯考虑公式的理解方面来说,我们发现状态转移方程从
dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i]) 这样,转变为
-
dp[i][j] = max(dp[i][j], dp[i][j - v[i]] + w[i]) 这样
我们直观来看,其实后者也就是在第二个部分从i-1变成了i 。01背包问题我们是如何理解这个i-1的?也就是从第i行的前一行得出这一行的结果。那么这里变成了 i ,也就是说,我们本行的结果,就是通过本行的数据来得出的。为什么?就因为一件物品可以被选多次。
我们想一下在01背包的一维数组优化里,我们说需要逆序遍历,是因为我们要用上一层的数据,如果正序的话,我们一件物品就会被选多次,得到的就是更改之后的数据。那么按照这个想法,运用到完全背包问题里,正好就是解决完全背包的方法。因此完全背包的一维优化我们可以直接这样理解。
我之前会有疑惑,正序遍历所造成的一件物品选择多次,这个多次是否涵盖了完全背包问题的所有可能次数?会不会有漏掉的情况呢?
答案是不会的,我们每一次正序遍历都会尝试在当前容量下选择当前物品,直到背包容量不足以再选择当前物品为止。因此,正序遍历实际上已经尝试了所有可能的选择次数,不会有漏掉的情况。
细品hh~
好啦,完全背包写到这里。
有问题欢迎指出,一起加油!!