背包问题是经典的DP问题。
背包可分为下面几类
- 01背包
- 完全背包
- 多重背包
- 分组背包
01背包问题
问题
有n个物品,每个物品都有体积v和价值w。还有一个背包的体积为V,问背包能装的最大价值是多少。
分析问题
状态表示:dp[i][j]:前i个物品放进体积为j的背包中,背包中的最大价值
状态计算:主要就是考虑当前第i个物品选还是不选。
如果当前背包容量>当前物品的体积是可选的:v[i]>=j
选:dp[i][j]=dp[i-1][j-v[i]]+w[i];
不选:dp[i][j]=dp[i-1][j];
由于取的是最大值,所以在两者之前取max就可以了。Dp[n][V]就是答案;
代码
//01背包
#include<bits/stdc++.h>
using namespace std;
const int N = 1005;
int dp[N][N];
int v[N],w[N];
int n,m;
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=1;j<=m;j++)
{
if(v[i]<=j)//可选时取选与不选之间最大值。
dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i]);
else //不可选
dp[i][j]=dp[i-1][j];
}
}
cout<<dp[n][m]<<endl;
return 0;
}
我们可以发现此代码还可以继续优化,我们每次进行状态计算的时候,使用的是当前位置上面一行的当前列之前的数据。所以可以优化为一维数组进行滚动。
//01背包使用一维数组优化
#include<bits/stdc++.h>
using namespace std;
const int N = 1005;
int dp[N];
int v[N],w[N];
int n,m;
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=m;j>=1;j--)//由于需要使用上一行前j列的数据,
{//所以需要从后往前遍历,避免更改需要使用的数据
if(v[i]<=j)
dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
}
}
cout<<dp[m]<<endl;
return 0;
}
完全背包
问题
有n个物品,每个物品都有体积v和价值w。还有一个背包的体积为V,每个物品可以使用无限次,问背包能装的最大价值是多少。
分析问题
状态表示:dp[i][j]:前i个物品放入多个放进体积为j的背包中,背包中的最大价值
状态计算:由于每个物品可以选无限次,所以需要划分集合
(第i个物品选0个,1个,2个……k个);然后取最大值就行
Dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i],dp[i-1][j-kv[i]]+kw[i]);
但是这样需要3层循环,算法效率不高,我们需要继续优化;
Dp[i][j-v[i]]=max(dp[i-1][j-v[i]]+w[i],dp[i-1][j-kv[i]]+kw[i]);
合并之后:dp[i][j]=max(dp[i-1][j],dp[i][j-v[i]]);
代码
//完全背包
#include<bits/stdc++.h>
using namespace std;
const int N = 1005;
int dp[N][N];
int v[N],w[N];
int n,m;
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=1;j<=m;j++)
{
dp[i][j]=dp[i-1][j];
if(v[i]<=j)
dp[i][j]=max(dp[i][j],dp[i][j-v[i]]+w[i]);
}
}
cout<<dp[n][m]<<endl;
return 0;
}
多重背包
问题
有n个物品,每个物品都有体积v和价值w。还有一个背包的体积为V,每个物品有s[i]个,问背包能装的最大价值是多少。
分析问题:
多重背包与完全背包比较类似,只是限制了每个物品的个数
状态表示:dp[i][j]:前i个物品放入多个放进体积为j的背包中,背包中的最大价值
状态计算:由于每个物品可以选无限次,所以需要划分集合
(第i个物品选0个,1个,2个……k个);然后取最大值就行k=min(j/v[i],s[i]);
但是由于可使用的是有限个,不可以像完全背包那样优化。
这里我们可以使用二进制来解决(二进制永远的神yyds),比如某个物品有11个:11(1011)=7(0111)+4(0100);就是将11分为比11小的数二进制末尾全是1的最大的的数+剩余的数,然后可以将11个物品分为4堆:1(0001),2(0010),4(0100),4(0100),这4堆物品可以分别选和不选然后可以结合成 0-11个物品,分完之后每推最多只能用一次,将这4推放物品集合中,有变成了01背包问题。
代码:
//多重背包问题
#include<bits/stdc++.h>
using namespace std;
const int N = 2005*11;
int dp[N];
int v[N],w[N];
int n,m;
int main()
{
cin>>n>>m;
int cnt=0;
for(int i=1;i<=n;i++)
{
int a,b,s;
cin>>a>>b>>s;
int k=1;
while(k<=s)//将s分解然后装进物品集合中 例如11(1011)分为0001,0010,0100;
{
v[++cnt]=a*k;
w[cnt]=b*k;
s-=k;
k<<=1;
}
if(s>=0)//最后剩下11(1011)-7(0111)=4
{
v[++cnt]=a*s;
w[cnt]=b*s;
}
;
}
for(int i=1;i<=cnt;i++)//对物品集合做01背包
{
for(int j=m;j>=1;j--)
{
if(v[i]<=j) dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
}
}
cout<<dp[m]<<endl;
return 0;
}
分组背包问题:
问题
有n组物品,每个物品都有体积v和价值w。还有一个背包的体积为V,每组最多只能选一个物品,问背包能装的最大价值是多少。
分析问题
状态表示:dp[i][j]:前i组物品放进体积为j的背包中,背包中的最大价值
状态计算:由于每组物品只能使用一个。需要划分集合
当前组(选0个、选第1个、第2个、第3个……第k个);
Dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i][1]]+w[i][1],……,dp[i-1][j-v[i][k]]+w[i][k])
代码:
//分组背包
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int n, m;
int v[N][N], w[N][N], s[N];
int dp[N];
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i ++ )
{
cin >> s[i];
for (int j = 1; j <= s[i]; j ++ )
cin >> v[i][j] >> w[i][j];
}
for (int i = 1; i <= n; i ++ )
for (int j = m; j >= 0; j -- )
for (int k = 1; k <= s[i]; k ++ )
if (v[i][k] <= j)
dp[j] = max(dp[j], dp[j - v[i][k]] + w[i][k]);
cout << dp[m] << endl;
return 0;
}