背包问题
一:01背包
(1):01背包分析
简单描述:就是有n个物品,包的体积为v,每个物品分别体积 和 价值为vi,wi,问得到的最大价值是多少。
首先我们对每一个物品都有两种选择,选或不选所以设f[i,j]的意思为在前i个物品中选择体积不超过j。
因为仅有两种选择,所以f[i,j]=max(f[ i - 1, j ],f[ i - 1, j - v ] + w );其中一个是在前面i-1个选择完了,不选择第i个,另一个是选择第i个.
(2):二维背包代码
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;
const int maxn=1010;
int w[maxn],v[maxn];
int f[maxn][maxn];
int main()
{
int N,V;
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++)
{
if(j<v[i]) f[i][j]=f[i-1][j];
else
f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i]);
}
}
printf("%d",f[N][V]);
return 0;
}
(3):空间优化代码
#include<iostream>
using namespace std;
#define N 1005
int dp[N];
int main()
{
int n,m,v,w;
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>v>>w;
for(int j=m;j>=v;j--)
{
dp[j]=max(dp[j],dp[j-v]+w);
}
}
cout<<dp[m]<<endl;
return 0;
}
这里我们可以观察到在二维体积遍历时时倒着往前遍历,因为观察原始方程,我们可以发现后面的两个式子都是需要的i-1的即上一维的数据,如果我们从小到大遍历,那么dp[5]的数据应该是dp[5] 需要的数据可能要上一维的dp[4],而在遍历dp[5]的时候第i维的dp[4]已经计算出来了,所以更新是由第i维实现的,而不是由i-1维。而倒着就因为前面的还没有更新,所以能够与原来的等价。
二:完全背包
(1):完全背包分析
有n种物体,每个物体都可以选择无限次,每个物体的价值和体积分别为 wi vi,f[i,j]分别表示在前i个物品中选择体积不超过j;
(2):方程推导
f[ i , j ] = max( f[ i - 1 , j ] , f[ i - 1 , j - v ] + w , f[ i - 1 , j - 2v ] + 2w , f[ i - 1 , j - 3v ] + 3w …)
f[ i , j - v ] = max(f[ i - 1 , j - v ] , f[ i - 1, j - 2v] + w, f[ i - 1 , j - 3v ] + 2w…)
我们可以发现以上规律可以写为 f[ i , j ] = max( f[ i - 1 , j ] , f[ i , j - v ] + w ) ;
(3):完全背包代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1005;
int f[N][N], w[N], v[N];
int main()
{
int n, m;
cin >> n >> m;
for(int i = 1; i <= n; ++i)
cin >> w[i] >> v[i];
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= m; ++j)
{
if(j < w[i]) f[i][j] = f[i - 1][j];
else f[i][j] = max(f[i - 1][j], f[i][j - w[i]] + v[i]);
}
cout << f[n][m] << endl;
return 0;
}
(4):空间优化的代码
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=1010;
int v[maxn],w[maxn];
int f[maxn];
int main()
{
int n,m;
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++)
{
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
}
printf("%d\n",f[m]);
return 0;
}
优化说明:我们发现在这里完全背包和01背包有所不同,这里的第二项是i而不是i-1,所以要从0开始,第一个而第一个i-1不必考虑,因为每次计算的时候都是从上次计算的,而第二个在计算f[ i , j ] 时,f[ i , j - v ]在本层已经计算过所以从0开始遍历。
三:多重背包
(1):多重背包分析
每个物品最多可以选择s个,其他的和其余背包相同。
(2):多重背包代码
#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
int f[110];
int main()
{
int n,v,m,w,s;
cin>>n>>m;
for(int i=0;i<n;i++)
{
cin>>v>>w>>s;
for(int j=1;j<=s;j++)
{
for(int k=m;k>=v;k--)
f[k]=max(f[k],f[k-v]+w);
}
}
printf("%d\n",f[m]);
return 0;
}
分析:对于这个多重背包的理解,我认为就是在枚举个数后和01背包是一样的,我们可以发现这体积的循环在最内层,我们可以把外面两层循环看做是对n * s 种情况的枚举,可以看做01背包。
(3):多重代码时间优化
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
int dp[2020];
struct good
{
int w,v;
};
int main()
{
vector<good> goods;
int N,V,w,v,s;
cin>>N>>V;
for(int i=1;i<=N;i++)
{
cin>>v>>w>>s;
for(int k=1;k<=s;k*=2)
{
s-=k;
goods.push_back({k*w,k*v});//将多重背包中的含有多种同一物品的情况例如 7 化为 1 2 4 情况这样就可以就可以将一个问题化为选与不选的问题。
}
if(s>0) goods.push_back({s*w,s*v});
}
for(auto t:goods)
{
for(int j=V;j>=t.v;j--)
{
dp[j]=max(dp[j],dp[j-t.v]+t.w);
}
}
printf("%d\n",dp[V]);
return 0;
}
分析:这个是二进制优化,优化的原理是在0~n的范围内,通过1不断乘以2记录下每个数,知道大于n, 这些数可以组成每个0 ~ n的数,例如:7:1,2,4。然后把s这一维给优化了,通过分成1,2,4这些数,原本的枚举现在可以变成选还是不选的。我认为他优化点就是去了一层循环。
四:混和背包
(1):混合背包分析
三种背包的混合
(2):混合背包代码
//主要思路就是将多重背包转化为01背包,然后01 和完全分情况讨论
#include<iostream>
#include<algorithm>
#include<vector>
#include<cstring>
#include<cstdio>
using namespace std;
int dp[2020];
struct good
{
int w,v,s;
};
int main()
{
vector<good> goods;
int N,V,v,w,s;
cin>>N>>V;
for(int i=1;i<=N;i++)
{
cin>>v>>w>>s;
if(s==-1) goods.push_back({w,v,-1});
if(s==0) goods.push_back({w,v,0});
if(s>0)
{
for(int j=1;j<=s;j*=2)
{
s-=j;
goods.push_back({j*w,j*v,-1});
}
if(s>0) goods.push_back({s*w,s*v,-1});
}
}
for(auto t:goods)
{
if(t.s==-1)
{
for(int j=V;j>=t.v;j--)
dp[j]=max(dp[j],dp[j-t.v]+t.w);
}
else if(t.s==0)
{
for(int j=0;j<=V;j++)
if(j>=t.v)
dp[j]=max(dp[j],dp[j-t.v]+t.w);
}
}
printf("%d\n",dp[V]);
return 0;
}
这个的解题思路就是将多重背包转化为01背包,然后再分别处理01和完全
五:二维费用背包
(1):二维费用背包分析
多加了一个限制条件,例如,受限制的不仅是背包体积还有背包的承重量
(2):二维费用背包代码
#include<iostream>
#include<algorithm>
using namespace std;
int dp[2020][2020];
int main()
{
int N,V,M,m,v,w;
cin>>N>>V>>M;
for(int i=1;i<=N;i++)
{
cin>>v>>m>>w;
for(int j=V;j>=v;j--)
for(int k=M;k>=m;k--)
{
dp[j][k]=max(dp[j][k],dp[j-v][k-m]+w);
}
}
printf("%d\n",dp[V][M]);
return 0;
}
这个的解题思路就是无非是多加了一个最大承重量,因为这里k和v是息息相关的,代码中也有体现,两者是同是加减的,减去一个物品体积的同时,也要减去他的重量,所以体积是倒序的,质量自然也是倒序的。
六:分组背包
(1):分组背包分析
就是一类物品中不一定有一个物品,但只能选择一个。
(2):分组背包代码
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
int v[110][110],w[110][110],dp[110],s[110];
int main()
{
int N,V;
cin>>N>>V;
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=V;j>=0;j--)
{
for(int k=1;k<=s[i];k++)//转化成01背包问题
{
if(j>=v[i][k])
dp[j]=max(dp[j],dp[j-v[i][k]]+w[i][k]);
}
}
}
printf("%d\n",dp[V]);
return 0;
}
这个的解题思路就是也转化成01背包,但是我们可以发现,这个和多重背包的区别是这个对体积的遍历在二维,也就是和分组背包的区别之处,这里在二维的目的是在三维里遍历物品,保证每类物品仅仅可以选择一个,看选哪个物品最好。