01背包问题:给你多个物品每个物品只能选一次,要你在不超过背包容积(或者恰好等于)的情况下选择装价值最大的组合。如果没有动态规划的基础其实是很难理解这个问题的,所以看这篇文章之前先去学习一下动态规划的基本思想。
对于这个问题我们先定义状态表示:dp[i][j]表示在1到i个物品中选择容积不超过j(或者恰好等于j)的最大价值(我们的下标从1开始,所以对于题目给的数组我们一般需要头插一个不影响后面填表的数一般就是0)
推导状态转移方程:
对于第i个物品我们其实有两种选择,一种是不选,一种就是选。
当我们不选择第i个物品时,那么对于在1到i个物品中选择容积不超过j(等于j)的最大价值的物品其实是和从1到i-1个物品中选择容积不超过j(等于j)的最大价值的物品。
当我们选择第i个物品那么首先我们需要判断的就是我们的容积j必须大于等于(等于,并且我们的dp[i-1][j-v[i]]必须要有意义,比如我们在初始化时规定-1是没有意义的那么我们不仅要保证j>=v[i] && dp[i-1][j-v[i]] != -1)我们的第i个物品的体积,然后我们的dp[i][j]其实就和我们在前i-1个物品中选择容积为j-v[i]的最大价值加上我们的第i个物品的价值。
初始化这里有视情况而定。
填表一般就是从上往下,从左往右,这里说的是未优化版本,如果我们进行优化那么就要判断到底是从左往右还是从右往左。
背包问题的优化一般很简单,就是通过滚动数组来实现,我们在代码上的体现就是删掉i的哪一维,然后改变一下填表顺序就可以了。
下面来一道题:【模板】01背包_牛客题霸_牛客网
我这里提供两种版本一种是未优化的,一种是优化的
//未优化版本
#include<iostream>
using namespace std;
const int N = 1010;
int n,V,v[N],w[N];
int dp[N][N];
int main()
{
int ret1 = 0;
cin>>n>>V;
for(int i = 1;i<=n;i++) cin>>v[i]>>w[i];
for(int i = 1;i<=n;i++)
for(int j = 1;j<=V;j++)
{
dp[i][j] = dp[i-1][j];
if(j>=v[i]) dp[i][j] = max(dp[i][j],
dp[i-1][j-v[i]]+w[i]);
ret1 = max(ret1,dp[i][j]);
}
cout<<ret1<<endl;
//第二问的初始化
for(int i = 1;i<=V;i++) dp[0][i] = -1;
int ret2 = -1;
for(int i = 1;i<=n;i++)
for(int j = 1;j<=V;j++)
{
dp[i][j] = dp[i-1][j];
if(j>=v[i] && (dp[i-1][j-v[i]] != -1))
dp[i][j] = max(dp[i][j],dp[i-1][j-v[i]]+w[i]);
}
for(int i = 1;i<=n;i++) ret2 = max(ret2,dp[i][V]);
if(ret2 == -1) cout<<0<<endl;
else cout<<ret2<<endl;
return 0;
}
//优化版本
#include<iostream>
using namespace std;
const int N = 1010;
int n,V,v[N],w[N];
int dp[N];
int main()
{
int ret1 = 0;
cin>>n>>V;
for(int i = 1;i<=n;i++) cin>>v[i]>>w[i];
for(int i = 1;i<=n;i++)
for(int j = V;j>=v[i];j--)
{
dp[j] = max(dp[j],dp[j-v[i]]+w[i]);
ret1 = max(ret1,dp[j]);
}
cout<<ret1<<endl;
//第二问的初始化
for(int i = 1;i<=V;i++) dp[i] = -1;
int ret2 = -1;
for(int i = 1;i<=n;i++)
for(int j = V;j>=v[i];j--)
{
if((dp[j-v[i]] != -1))
dp[j] = max(dp[j],dp[j-v[i]]+w[i]);
}
for(int i = 1;i<=n;i++) ret2 = max(ret2,dp[V]);
if(ret2 == -1) cout<<0<<endl;
else cout<<ret2<<endl;
return 0;
}
完全背包问题:与上面的01背包问题的不同在与这类问题中我们的同一个物品是可以选择无数个的,那么我们的状态转移方程选择第i个物品的时候就不可以沿用01背包的状态转移方程,因为这里有点麻烦我就放图片了
通过这一过程我们发现其实dp[i][j]是等于max(dp[i-1][j],dp[i][j-v[i]]+w[i])的但是对于dp[i][j-v[i]]这个地方我们需要先判断有没有意义,才能进行后续的填值。
做个题目:【模板】完全背包_牛客题霸_牛客网
依旧提供两个版本:
我提供的第一个版本里的dp[i][j]选择第i个物品时有两种写法一种是循环一种是我的图片那种方式,但是都不会超时最后用数学的那种是因为美观
//未优化版本
#include<iostream>
#include<vector>
int max(int x,int y) {return x>y?x:y;}
int main()
{
int n,V;std::cin>>n>>V;
std::vector<int> v(n),w(n);
for(int i = 0;i<n;i++) std::cin>>v[i]>>w[i];
std::vector<std::vector<int>> dp(n+1,std::vector<int>(V+1));
v.insert(v.begin(),0),w.insert(w.begin(),0); int _max = 0;
for(int i = 1;i<=n;i++)
for(int j = 1;j<=V;j++)
{
dp[i][j] = dp[i-1][j];
// int val = 1;
// for(int k = v[i];k<=j;k+=v[i])
// {
// dp[i][j] = max(dp[i][j],dp[i-1][j-val*v[i]]+val*w[i]);
// val++;
// }
if(j>=v[i]) dp[i][j] = max(dp[i][j],dp[i][j-v[i]]+w[i]);
_max = max(_max,dp[i][j]);
}
std::cout<<_max<<std::endl;
for(int i = 0;i<=n;i++) dp[i].clear();
for(int i = 1;i<=V;i++) dp[0][i] = -1;
for(int i = 1;i<=n;i++)
for(int j = 1;j<=V;j++)
{
dp[i][j] = dp[i-1][j];
// int val = 1;
// for(int k = v[i];k<=j;k+=v[i])
// {
// if(dp[i-1][j-val*v[i]] != -1)
// dp[i][j] = max(dp[i][j],dp[i-1][j-val*v[i]]+w[i]*val);
// val++;
// }
if(j>=v[i] && dp[i][j-v[i]] != -1)
dp[i][j] = max(dp[i][j],dp[i][j-v[i]]+w[i]);
}
if(dp[n][V] != -1) std::cout<<dp[n][V]<<std::endl;
else std::cout<<"0"<<std::endl;
return 0;
}
#include<iostream>
#include<vector>
int max(int x,int y) {return x>y?x:y;}
int main()
{
int n,V;std::cin>>n>>V;
std::vector<int> v(n),w(n);
for(int i = 0;i<n;i++) std::cin>>v[i]>>w[i];
std::vector<int> dp(V+1);
v.insert(v.begin(),0),w.insert(w.begin(),0); int _max = 0;
for(int i = 1;i<=n;i++)
for(int j = v[i];j<=V;j++)
{
// int val = 1;
// for(int k = v[i];k<=j;k+=v[i])
// {
// dp[j] = max(dp[j],dp[j-val*v[i]]+val*w[i]);
// val++;
// }
if(j>=v[i]) dp[j] = max(dp[j],dp[j-v[i]]+w[i]);
_max = max(_max,dp[j]);
}
std::cout<<_max<<std::endl;
dp.clear();
for(int i = 1;i<=V;i++) dp[i] = -1;
for(int i = 1;i<=n;i++)
for(int j = v[i];j<=V;j++)
{
// int val = 1;
// for(int k = v[i];k<=j;k+=v[i])
// {
// if(dp[j-val*v[i]] != -1)
// dp[j] = max(dp[j],dp[j-val*v[i]]+w[i]*val);
// val++;
// }
if(j>=v[i] && dp[j-v[i]] != -1) dp[j] = max(dp[j],dp[j-v[i]]+w[i]);
}
if(dp[V] != -1) std::cout<<dp[V]<<std::endl;
else std::cout<<"0"<<std::endl;
return 0;
}