一.01背包问题(从集合的方面理解)
01背包问题:现有 N 件物品和一个最多能承重 M 的背包,第 i 件物品的重量是 wi ,价值是 vi。在背包能承受的范围内,试问将哪些物品装入背包后可使总价值最大,求这个最大价值。
eg:
思路:
注:此图也可以当作分析背包问题的一个思路
1.理解f[i][j]:指在前i个物体中选择体积不超过j的最优解
2.集合划分:这道题我们可以把大的集合划分为两个部分:(1)选择第i个元素(2)不选择第i个元素
第一部分选了第i个元素:其实就是在i-1个里面选择体积不超过j-v[i]的最优解再加上第i个元素的价值,由此得到公式:f[i][j]=f[i-1][j-v[i]]+w[i];
第二部分: 不选第i个就是在i-1个里面选择不超过j的最优选,由此得到公式发f[i][j]=f[i-1][j];
最后得到公式:f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i]);
法1:朴素算法
#include<bits/stdc++.h>
using namespace std;
const int N=1100;
int v[N],w[N];
int f[N][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++)
{
f[i][j]=f[i-1][j];
if(v[i]<=j)f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);
}
cout<<f[n][m]<<endl;
return 0;
}
优化:(在朴素算法上将二维数组优化为一维数组)(滚动数组)
#include<bits/stdc++.h>
using namespace std;
const int N=1100;
int v[N],w[N];
int f[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>=v[i];j--)
/*逆序更新:因为 j-v[i] 小于等于j,所以如果j从0开始规划,那么在更新“选取物品i的选择方案”时,会用本轮(第i个物品规划)的比较小的物品容量j时被更新过的数据,这个数据反映的是本轮针对第i个物品的规划,不是第i-1个物品的规划,所以与原始算法不等价,采取倒序的方式更新,这样可以保留上一轮i-1的信息。*/
{
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
cout<<f[m]<<endl;
return 0;
}
二.完全背包问题
完全背包问题:完全背包问题一般是指:有N件物品和一个能背重量为W的背包,第i件物品的重量为weight[i],价值为value[i]。每件物品有无限个(也就是可以放入背包多次),求怎样可以使背包物品价值总量最大。
eg:
1思路:
按上面01的思维导图来思考
重点:集合划分:第i个元素选择几个
f[i][j]=f[i-1][j-k*v[i]]+k*w[i]
法1:朴素做法
#include<iostream>
using namespace std;
const int N = 1010;
int f[N][N];
int v[N],w[N];
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 = 0 ; j<=m ;j++)
{
for(int k = 0 ; k*v[i]<=j ; k++)
f[i][j] = max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);
}
cout<<f[n][m]<<endl;
}
法2:优化
思路:
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])
由此可以知道k循环可以不要
for(int i = 1 ; i<=n ;i++)
for(int j = 0 ; j<=m ;j++)
{
f[i][j]=f[i-1][j];
//for(int k = 0 ; k*v[i]<=j ; k++)
if(_v[i]<=j)
f[i][j] = max(f[i][j-v]+w,f[i-1][j]);
}
可以按照01的优化方式再进一步优化
最终代码:
#include<bits/stdc++.h>
using namespace std;
const int N=1100;
int v[N],w[N];
int f[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=v[i];j<=m;j++)
{
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
cout<<f[m]<<endl;
return 0;
}
三.多重背包问题
eg:
思路:
集合划分:第i个数选择几个
法1:朴素做法
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 110;
int n, m;
int v[N], w[N], s[N];
int f[N][N];
int main()
{
cin >> n >> m;
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 <= m; j ++ )
for (int k = 0; k <= s[i] && k * v[i] <= j; k ++ )
f[i][j] = max(f[i][j], f[i - 1][j - v[i] * k] + w[i] * k);
cout << f[n][m] << endl;
return 0;
}
优化:当数据过大时候,朴素算法就会超时,此时我们用二进制进行优化,转换成01 背包问题;
如何使用二进制优化:
我们知道任意一个实数可以由二进制数来表示,比如1023我们可以用 1,2,4,8,16,32,64,128,256,512来表示1~1023任意个数字
这样利用二进制优化,时间复杂度就从 O(n3)降到O(n2logS),从4∗1e9降到了2∗1e7
#include<bits/stdc++.h>
using namespace std;
const int N = 12010, M = 2010;
int v[N],w[N];//逐一枚举最大是N*logS
int f[M];
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)
{
cnt++;
v[cnt]=a*k;
w[cnt]=b*k;
s-=k;
k*=2;
}
if (s > 0)
{
cnt ++ ;
v[cnt] = a * s;
w[cnt] = b * s;
}
}
n=cnt;
for(int i=1;i<=n;i++)
for(int j=m;j>=v[i];j--)
f[j]=max(f[j],f[j-v[i]]+w[i]);
cout<<f[m]<<endl;
return 0;
}
四.分组背包问题
eg:
朴素做法
#include<bits/stdc++.h>
using namespace std;
const int N=110;
int f[N][N]; //只从前i组物品中选,当前体积小于等于j的最大值
int v[N][N],w[N][N],s[N]; //v为体积,w为价值,s代表第i组物品的个数
int n,m,k;
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>s[i];
for(int j=0;j<s[i];j++){
cin>>v[i][j]>>w[i][j]; //读入
}
}
for(int i=1;i<=n;i++){
for(int j=0;j<=m;j++){
f[i][j]=f[i-1][j]; //不选
for(int k=0;k<s[i];k++){
if(j>=v[i][k]) f[i][j]=max(f[i][j],f[i-1][j-v[i][k]]+w[i][k]);
}
}
}
cout<<f[n][m]<<endl;
}
优化(与01相似的优化)
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 110;
int n, m;
int v[N][N], w[N][N], s[N];
int f[N];
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i ++ )
{
cin >> s[i];
for (int j = 0; 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 = 0; k < s[i]; k ++ )
if (v[i][k] <= j)
f[j] = max(f[j], f[j - v[i][k]] + w[i][k]);
cout << f[m] << endl;
return 0;
}