思路来源:https://blog.csdn.net/yoer77/article/details/70943462
动态规划:
动态规划算法是一种根据先决量来推导后量的一种算法,它的核心思想是状态转移:每一个新的状态量都可以通过已知的一个旧的状态量来推得,例如,当我们想要知道选取三件物品的最优解时,可以从选取两件物品的最优解中推导过来,以此类推。
(一)01背包基础知识:
在介绍01背包前,先介绍一下背包问题,什么是背包问题呢?它又和动态规划有什么关系呢?我们来举个最经典的例子:
假如现在有四件物品,重量为1,2,3,4 价值为3,2,4,5 ,而现在我们有一个承重为6的背包,那我们该怎样选取物品才能保证我们得到的物品总价值最大呢?而动态规划算法就是为了解决类似的问题而诞生的。
算法分析:我们可以开辟一个二维数组dp[MAX_N][MAX_W]来帮助我们去得到这个问题的最终答案,
dp[i][j]表示的是在前i件物品,背包j大小时的最优解
例如:dp[2][3]记录的就是在只有前两件物品时,背包只有大小3时的最优解
具体代码:
void DP(int n,int m){
for(int i=1;i<=n;i++){//i是物品数
for(int j=1;j<=m;j++){//j是物品承重
if(j<w[i])
dp[i][j]=dp[i-1][j];
else
dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
}
}
}
一层for:物品遍历:从第1件物品开始遍历到第n件物品
二层for:承重遍历:从承重1开始遍历到承重w ,将每种承重下的最优解记录到dp数组中
对于每种状态:
if 当前承重<当前物品重量(太沉了装不下):这种状态下这件物品是放不进背包的,那么直接让这一轮的dp[j]=上一轮的dp[j],就好,换言之,其实就是不把这件物品放进去
else(如果能装下):这时我们就要比较了,是上一轮的当前承重下的总价值最大,还是把当前这间物品塞进去以后更大,哪个大取哪个,上一轮的状态是dp[i-1][j],而将这件物品塞进去的状态是dp[];
最后当把所有的物品遍历完后,dp[n][1—w]这个数组里就是每一种承重下的最大value,遍历一遍则得到最大value;
case 1:(上面的例子)
row1:4件物品 总承重6
row2-rown+1:weight、value
else:dp数组[1-n][1-w]
case 2:
row1:5 件物品 总承重10
row2-rown+1:value、weight
else:dp数组[1-n][1-w]
下面讲一下空间复杂度优化后算法
根据大佬们的说法:第i行存的是前i种物品下j承重的最大value,那么如果我们求的是所有物品(也就是所谓的n件物品)的最大value的话,其实我们只需要用最后一行来存就可以了,但是问题在于我们在讨论第i件物品时,需要i-1时的状态来帮助我们往下推,那么怎么解决这个问题呢?其实很简单(简单个P),就是在每次讨论第i件物品的开始(第二个for循环开始),这时的数组里存的还是i-1时的状态,我们每次遍历时其实用到的都是[i-1][j]或者[i-1][j-w[i]],能够发现用的都是比此称重下小的状态
所以!我们只需要反着遍历就可以了,从承重w—1,这样就ok了,我们在用到之前状态时,还没覆盖;
具体代码如下:
for(int i=1;i<=N;i++)//i是物品数
{
for(int j=W;j>=0;j--)//j是物品承重
{
if(j<w[i])continue;
else
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
}
}
Case 3:
样例仍是Case 2的样例,但数组却只存储了最后一行,空间复杂度下降到O(n)
(二)例题与讲解:
1、原题链接:https://www.luogu.org/problemnew/show/P1060
思路分析:简单01背包模板题,背包大小就是n,物品的重量w就是价格,而价值v就是价格*重要度
AC代码:
#include<bits/stdc++.h>
#define MAX_N 30005
using namespace std;
int dp[MAX_N],w[30],v[30];
int main(){
memset(dp,0,sizeof(dp));
int m,n;//money number
cin>>m>>n;
for(int i=0;i<n;i++){
int a,b;
cin>>a>>b;
w[i]=a;
v[i]=a*b;
}
for(int i=0;i<n;i++){
for(int j=m;j>w[i];j--){
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
}
}
cout<<dp[m]<<endl;
}
2、原题链接:https://www.luogu.org/problemnew/show/P1164
思路分析:这题问的是方案数,一般方案数的东西都是一个累加过程,当前状态数是上一种状态数的累加,这题根据题意可推出,在有3块钱买2块钱的东西时的方案数,就是你有1块钱的方案数;
状态转移方程为:dp[ j ]+=dp[ j-w[ i ] ]
AC代码:
#include<bits/stdc++.h>
#define MAX_N 10005
#define INF 0x3f3f3f3f
using namespace std;
int dp[MAX_N],w[105];
void init(){
for(int i=0;i<MAX_N;i++)dp[i]=0;
}
int main(){
init();
dp[0]=1;
int n,m;
cin>>n>>m;
for(int i=0;i<n;i++){
cin>>w[i];
}
for(int i=0;i<n;i++){
for(int j=m;j>=w[i];j--){
dp[j]+=dp[j-w[i]];
}
}
cout<<dp[m]<<endl;
}