多重背包

多重背包:

有N种物品和一个体积为V的背包。第i种物品最多有n[i]件可用,每件体积是w[i],价值是v[i]。将哪些物品装入背包可使这些物品的体积总和不超过背包体积,且价值总和最大?

动态规划:

dp[i][j]表示前i种物品放入体积为j的背包中的最大价值。

状态转移方程:

dp[i][j] = max{ f[i-1][j - k*w[i]] + k*v[i]},0 <= k <= n[i]

完全按照此思路,代码为三层循环,复杂度达 O(NVΣni)。可以和完全背包一样,借鉴01背包的思想,进行优化。优化后,复杂度为O(NVΣlogni)。

优化采用二进制思想,我们考虑把第 i 种物品换成若干件物品,使得原问题中第i 种物品可取的每种策略——取 0...ni,均能等价于取若干件代换以后的物品。另外,取超过ni件的策略必不能出现。
方法是:将第 i 种物品分成若干件 01 背包中的物品,其中每件物品有一个系数。这件物品的体积和价值均是原来的体积和价值乘以这个系数。令这些系数分别为1,2,2^2......2^(k−1),ni − 2^k + 1 ,且 k 是满足ni − 2^k + 1 > 0 的最大整数。例如,如果ni为 13 ,则相应的 k = 3 ,这种最多取 13 件的物品应被分成系数分别为 1,2,4,6 的四件物品。分成的这几件物品的系数和为ni,表明不可能取多于ni件的第 i 种物品。另外这种方法也能保证对于 0......ni间的每一个整数,均可以用若干个系数的和表示。这里算法正确性的证明可以分 0...2^(k−1)和 2^k......ni两段来分别讨论得出。

证明如下:

(1) 数列1,2,2^2......2^(k - 1),n - 2^k + 1中所有元素的和为n,所以若干元素的和的范围为:[1, n]。

(2)如果正整数t <= 2^k – 1,则t一定能用1,2,4......2^(k - 1)中某几个数的和表示,这个很容易证明:我们把t              的二进制表示写出来,很明显,t可以表示成n=a0*2^0 + a1*2^1 + ...... + ak*2^(k - 1),其中ak=0或者1,表              示t的第k位二进制数为0或者1。

(3)由算法思路,n一定 <= 2^k * 2。如果t  >=  2^k,设s = n - 2^k + 1,则t - s <= 2^k - 1,因而t - s可以表示成                  1,2,2^2......2^(k - 1)中某几个数的和的形式,进而t可以表示成1,2,2^2......2^(k - 1),s 中某几个数的          和(加数中一定含有s)的形式。


#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int dp[10000];
int w[100],v[100],num[100];//多重背包:体积 价值 数量
int w1[3500],v1[3500];//转化为01背包:体积 价值
int main(){
  int N,V,index = 0;
  scanf("%d%d",&N,&V);
  for(int i = 1;i <= N;i++){
    scanf("%d%d%d",&w[i],&v[i],&num[i]);
  }
  for(int i = 1;i <= N;i++){
    int t = num[i],k = 1;
    while(t - k > 0){
      t -= k;
      w1[++index] = k * w[i];
      v1[index] = k * v[i];
    }
    w1[++index] = t * w[i];
    v1[index] = t * v[i];
  }
  memset(dp,0,sizeof(dp));
  for(int i = 1;i <= index;i++){
    for(int j = V;j >= w1[i];j--){
      dp[j] = max(dp[j],dp[j - w1[i]] + v1[i]);
    }
  }
  printf("%d\n",dp[V]);
  return 0;
}

另一种方法:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int N,V;
int dp[10000];
int w[100],v[100],num[100];//多重背包:体积 价值 数量
void zeroOnePack(int weight,int value){
  for(int i = V;i >= weight;i--){
    dp[i] = max(dp[i],dp[i - weight] + value);
  }
}
void completePack(int weight,int value){
  for(int i = weight;i <= V;i++){
    dp[i] = dp[i - weight] + value;
  }
}
void multipPack(int weight,int value,int num){
  if(V <= weight * num){
    completePack(weight,value);
    return;
  }
  else{
    int k = 1;
    while(num - k > 0){
      num -= k;
      zeroOnePack(k * weight,k * value);
      k *= 2;
    }
    zeroOnePack(num * weight,num * value);
  }
}
int main(){
  scanf("%d%d",&N,&V);
  for(int i = 1;i <= N;i++){
    scanf("%d%d%d",&w[i],&v[i],&num[i]);
  }
  memset(dp,0,sizeof(dp));
  for(int i = 1;i <= N;i++){
    multipPack(w[i],v[i],num[i]);
  }
  printf("%d\n",dp[V]);
  return 0;
}




  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值