各种背包
背包问题大概分下面这几种
*部分背包问题
*0 1 背包问题
*完全背包问题
*多重背包问题
分析
因为物体既有重量又有价值,所以不能简单的先拿轻的(轻的可能价值也小),也能先拿价值大的(它可能特别重),而因该综合考虑两个因素。一种直观的贪心策略就是:优先拿“价值除以重量”最大的,直到重量和正好为W
思路
那么你作为一个非常优秀的ACMer,肯定应该知道按照什么顺序拿物品的吧。没错,看着值钱的先抢!
解决
那么问题很简单咯,把” 值钱” 的东西排在前面,每次拿抢的时候,问问看背包君够不够承受得住,承受的了,就全部抢过来。承受不住,那么只能按照所能承受的重量,取物品的一部分了。当然价值也得按照比例来哦
0 1 背包问题:
有n 种重量和价值分别为wivi 的物品,从这些物品中挑选总重量不超过W 的物品,求出挑选物品价值总和的最大值。在这里,每种物品只可以挑选一件
限制条件
1 <= n <= 100
1 <= wi; vi <= 100
1 <= W <= 10000
分析一波
这道题就是各个物品“选”与“不选”的组合,因此被称为01 背包问题。如果检查n 个物品所有“选”与“不选”的组合,算法的复杂度为O(2n) 当物品的大小以及背包的大小均为正数,则01 背包问题可以用动态规划法以O(nW) 的效率解决。
状态转移方程
如果我们按照如下的方式来定义递推关系的话,刚刚关于i 的循环就能正向进行我们定义dp[i+1][j]:= 从0 到i 这i+1 个物品中选出总重量不超过j 的物品时总价值的最大值很显然dp[0][j]=0,因为你没物品可取的时候背包的价值为0
dp[i][j] (j < w[i])
dp[i + 1][j] ={
max(dp[i][j]; dp[i][j -w[i]] + v[i]) 其他
int dp[MAXN][MAXN]//dp[i][j]表示当背包剩余空间为j时,放第i个物品时最高的价值
for(int i=0;i<n;i++)//n为物品个数,w为背包的容量
for(int j=0;j<=W;j++){
if(j<w[i])
dp[i+1][j]=dp[i][j];
else
dp[i+1][j]=max(dp[i][j],dp[i][j-w[i]]+v[i]);
}
printf("%d\n",dp[n][W]);//二维数组
int dp[MAXN];//背包容量为j时的最大价值
for(int i=0 ; i<n ; i++)
for(int j=w ; j>=0 ; j--){//逆序,保证dp[v-w[i]]保存的状态是dp[i-1][v-w[i]];
if(w[i]<=j)
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
else dp[j]=dp[j];
}
printf("%d\n",dp[w]);//一维滚动数组
完全背包问题:
问题提出
有n 种重量和价值分别为w[i]v[i] 的物品,从这些物品中挑选总重量不超过W 的物品,求出挑选物品价值总和的最大值。在这里,每种物品可以挑选任意件
限制条件
1 <= n <= 100
1 <= wi; vi <= 100
1 <= W <= 10000
这次同一类的物品可以挑选任意多件了。我们再试着写出递推关系。另dp[i + 1][j] = 从前i 种物品中挑选总重量不超过j 时总价值的最大值。那么递推关系就为:
状态转移方程
dp[0][ j ] = 0
dp[i+1][ j ]=max(dp[ i ][ j - k*w[i] ]+k * v[ i ] | k*w[ i ]< = w)
这个代码并不友好,拥有三重循环O(nW2)上边的代码有很多重复计算,将状态转移方程化简一下得到的是这样子的
状态转移方程续
这样一来就不需要k 的循环了,便可以用O(nW) 时间解决问题
int dp[MAXN][MAXN]//dp[i][j]表示当背包剩余空间为j时,放第i个物品时最高的价值
for(int i=0;i<n;i++)//n为物品个数,w为背包的容量
for(int j=0;j<=W;j++){
if(j<w[i])
dp[i+1][j]=dp[i][j];
else
dp[i+1][j]=max(dp[i][j],dp[i+1][j-w[i]]+v[i]);
}
printf("%d\n",dp[n][W]);//二维数组
int dp[MAXN];//背包容量为j时的最大价值
for(int i=0 ; i<n ; i++)
for(int j=0 ; j<=w ; j++){//正序,保证dp[v-w[i]]保存的状态是dp[i][v-w[i]];因为物品可能有无限个,所以j的状态是从少放一个i那儿继承来的
if(w[i]<=j)
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
else dp[j]=dp[j];
}
printf("%d\n",dp[w]);//一维滚动数组
多重背包问题
有n 种重量和价值分别为w[i],v[i] 的物品,从这些物品中挑选总重量不超过W 的物品,求出挑选物品价值总和的最大值。不过这里,每种物品最多可以挑选mi 件
限制条件
1 <= n <= 100
1 <= wi; vi <= 100
1 <= mi <= 10000
1 <= W <= 10000
状态转移方程
dp[i] [ j ]= 到第i 个物品为止总重量不超过j 的所有选法中最有可能的最大值
dp[i + 1] [ j ] = max(dp[ i ] [ j - k * w[ i ] ] + k * v[i] || 0<= k<= m[ i ])
分析分析
对第i 种物品有mi + 1 件策略:取0 件,取1 件....... 取mi 件,复杂度是O(nmW),无法在规定时间内求解
分析分析
一种好想好写的基本方法是将其转化为01 背包问题求解:把第i 件物品换成mi 件01 背包中的物品,也就是说我们将mi 件物品变成0 ...mi 个物品,则得到的物品数为Σmi 的01 背包问题,如果直接求得话,复杂度仍然是O(nmW)
分析分析
但是我们希望将它转换成01 背包问题之后,能够想完全背包一样降低复杂度。考虑二进制的思想,把第i 件物品换成若干件物品,使得原问题中的第i 件物品可取的每一种策略——取0...mi 件——均能够等价于取若干件物品代换之后后的物品,另外,取mi 件的策略必不能够出现
将mi 分解为如下形式:
mi = 1 + 2 + 4 + :::: + 2^k + a(0 a 2k+1)
由于1; 2; ...., 2^k 的组合可以表示出 2^(k+1) - 1 的所有整数,因此1; 2; : : : 2^k; a 可以表示出所有 mi 的所有整数。因此,我们把
mi 个重量和价值分别为wi 和vi 的物品,看成重量和价值分别为wi *x; vi* x(x = 1; 2; : : : ; 2^k; a) 的k+2 个物品。这样,物品的
总个数就变为O(n log m) 个,使用一般的01 背包DP 可以在O(nW logm) 时间内求出答案
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long LL;
const int MAXN = 1e6 + 10;
LL v[110], w[110], c[110];
//多重背包:v表示价值,w表示空间,c表示个数
LL val[1010], size[1010];
//01背包:val表示新的价值,size表示新的空间
LL dp[MAXN];
int main() {
int n, m;
while(scanf("%d %d", &n, &m) != EOF) {
int count = 0;
for(int i = 0; i < n; i++) {
scanf("%lld %lld %lld", &c[i], &w[i], &v[i]);
for(LL j = 1; j <= c[i]; j << 1) {
val[count] = j * v[i];
size[count++] = j * w[i];
c[i] -= j;
}
if(c[i] > 0) {
val[count] = c[i] * v[i];
size[count++] = c[i] * w[i];
}
}
memset(dp, 0, sizeof(dp));
for(int i = 0; i < count; ++i) {
for(int j = m; j >= size[i]; --j) {
dp[j] = max(dp[j], dp[j - size[i]] + val[i]);
}
}
printf("%lld\n", dp[m]);
}
return 0;
}