01背包问题 每件物品只能用一次
完全背包 无限次用物品
多重背包问题 每件物品有有限件
分组背包问题 有有限组,每组中有若干个物品,每组只能选一个
混合背包 多种加和
dp分析法
状态表示f(i,j)
集合,状态表示的是哪一个集合
所有选法
条件:只从前i个物品那里选,总体积小于等于j
属性,该集合的属性如:max min 数量等
状态计算
集合的划分:不重不漏,不漏一定要满足,不重看情况
01背包
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int w[N],v[N];
int vol,n;
int dp[N][N];
int main()
{
cin>>n>>vol;
for(int i = 1;i<=n;i++){
cin>>v[i]>>w[i];
}
for(int i = 1;i<=n;i++){
for(int j = 1;j<=vol;j++){
if(j<v[i]) dp[i][j] = dp[i-1][j];//判断是否能选这件物品
else dp[i][j] = max(dp[i-1][j],dp[i-1][j-v[i]]+w[i]);
//状态转移,该状态可以由两个状态算出,一个是不选该物品,另一个是,选了该物品。
// dp[i-1][j] dp[i-1][j-v[i]]+w[i];
}
}
cout<<dp[n][vol];
return 0;
}
//滚动数组优化版
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int w[N],v[N];
int vol,n;
int dp[N];
int main()
{
cin>>n>>vol;
for(int i = 1;i<=n;i++){
cin>>v[i]>>w[i];
}
for(int i = 1;i<=n;i++){
for(int j = vol;j>=v[i];j--){
dp[j] = max(dp[j],dp[j-v[i]]+w[i]);
}
}
cout<<dp[vol];
return 0;
}
为什么能优化?
因为状态转移只用到了两层,i和i-1层,所以其他层不需要用,所以这样就可以把空间复杂度优化成一维。一维数组的含义是当取第i件物品时,在j的背包容量下,所能装下的最大的物品价值,
为什么要从vol减到v[i]?
如果正序更新的话,会出现数据被污染的情况,不优化的版本中,第i层的数据一定是i-1层更新的来的,但是优化成一维,会出现要用上一层的数据的时候,已经被更新过了,比如, 如果f[7]要 用f[3]来优化,如果正向就会出现,f[3]在f[7]更新之前就已经更新了,是第i层的数据而不是i-1层的数据
完全背包
朴素做法 就是枚举各个状态 不过TLE
#include <iostream>
using namespace std;
const int N = 1010;
int w[N],v[N];
int dp[N][N];
int n,vol;
int main()
{
cin>>n>>vol;
for(int i = 1;i<=n;i++) cin>>v[i]>>w[i];
for(int i = 1;i<=n;i++){
for(int j = 0;j<=vol;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][vol];
return 0;
}
优化版 三维优化为两维
f[i , j ] = max( f[i-1,j] , f[i-1,j-v]+w , f[i-1,j-2*v]+2*w , f[i-1,j-3*v]+3*w , .....)
f[i , j-v]= max( f[i-1,j-v] , f[i-1,j-2*v] + w , f[i-1,j-3*v]+2*w , .....)
由上两式,可得出如下递推关系:
f[i][j]=max(f[i,j-v]+w , f[i-1][j])
所以得到了第一次优化
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]>=0)
dp[i][j]=max(dp[i][j],dp[i][j-v[i]]+w[i]);
}
然而,还可以继续优化为一维,考虑到01背包的优化
#include<iostream>
using namespace std;
const int N = 1010;
int dp[N];
int v[N],w[N];
int main()
{
int n,vol;
cin>>n>>vol;
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<=vol ;j++)
{
dp[j] = max(dp[j],dp[j-v[i]]+w[i]);
}
cout<<dp[vol]<<endl;
}
多重背包问题
纯暴力与完全背包类似
#include <iostream>
using namespace std;
const int N = 110;
int w[N],v[N],s[N];
int dp[N][N];
int main()
{
int n,vol;
cin>>n>>vol;
for(int i = 1;i<=n;i++) cin>>v[i]>>w[i]>>s[i];
for(int i = 1;i<=n;i++)
for(int j = 0;j<=vol;j++)
for(int k = 0;k*v[i]<=j&&k<=s[i];k++){
dp[i][j] = max(dp[i][j],dp[i-1][j-k*v[i]]+w[i]*k);
}
cout<<dp[n][vol];
return 0;
}
但是,数据范围太大的时候,包超时的牢底。
下面是优化
如果参考完全背包的优化方法那么会得到
f[i,j] = max(f[i-1,j], f[i-1,j-v]+w, f[i-1,j-2v]+2w,.., f[i-1,j-sv] + sw )
f[i,j-v] = max( f[i-1,j-v], f[i-1,j-2v]+w,.., f[i-1,j-sv]+(s-1)w, f[i-1,j-(s+1)v]+sw )
我们发现永远无法把f[i,][j-v]里面的最后一项去掉,所以不能用完全背包的优化方法去优化
只有一条路:二进制优化方法
原理:二进制把每个数分成1 2 4 8 ......等二进制数,那么按照此等分割方式,我么可以把任意的数字分成几个二进制数字的和,所以多重背包中的物品数量就可以按组分成若干份。就不用一个一个枚举了,O(N)变为O(logN)---------就是把n个物品打包在一起,当成一个单独的物品
#include<iostream>
using namespace std;
const int N = 30010;
//为什么要开到30010?
//因为二进制的原因log2000 * 1000 约等于30000
int dp[N];
int w[N],v[N];
int main()
{
int n,vol;
cin>>n>>vol;
int cnt = 0;//记录分到了第几个组
for(int i = 1;i<=n;i++){
int a,b,s;
cin>>a>>b>>s;
int k = 1;
while(k<=s){
cnt++;
w[cnt] = k*b;
v[cnt] = k*a;
s-=k;
k*=2;
}
if(s>0){
cnt++;
w[cnt] = s*b;
v[cnt] = s*a;
s = 0;
}
}
for(int i = 1;i<=cnt;i++){//简单01
for(int j = vol;j>=v[i];j--){
dp[j] = max(dp[j],dp[j-v[i]]+w[i]);
}
}
cout<<dp[vol]<<endl;
return 0;
}
然后我们惊奇的发现,就相当于01背包问题了,magic。
分组背包问题
与上面类似
#include <iostream>
using namespace std;
const int N = 110;
int w[N][N],v[N][N];
int s[N];
int dp[N];
int main()
{
int n,vol;
cin>>n>>vol;
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 = vol;j>=1;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[vol];
return 0;
}
混合背包
都看成多重背包做二进制优化就行了,对于完全背包的的项,可以算出来最多有几个这一项就可以了-------背包最大容量/该物品的体积
#include <bits/stdc++.h>
using namespace std;
int a[10005],b[10005];
int t=0,n,m,f[10005],w,v,s;
int main()
{
cin>>n>>m;
while(n--){
cin>>v>>w>>s;
if(s==-1)s=1;
if(s==0)s=m/v;
else s=s;
for(int i=1;;i*=2){
if(s<i)break;
s-=i;
a[++t]=v*i;
b[t]=w*i;
}
if(s>0)a[++t]=v*s,b[t]=w*s;
}
for(int i=1;i<=t;i++)
for(int j=m;j>=a[i];j--)
f[j]=max(f[j-a[i]]+b[i],f[j]);
cout<<f[m]<<endl;
}