背包9讲_多重背包问题

在这里插入图片描述

主要思路:

f[i][j]=max(f[i-1][j-k*v[i]]+k*w[i]) (0=<k<=p)与完全背包同==,只是k不是固定,所以没法优化掉k
根据当前的动态规划方程写代码:

// 多重背包 未优化前
#include <iostream>
#include <algorithm>

const int MAXN = 1005;
int w[MAXN];  // 重量
int v[MAXN];  // 价值
int s[MAXN];  // 个数
int f[MAXN][MAXN]; // f[i][j], j重量下前i个物品的最大价值
int main(){
    int n,m;
    cin>>n>>m;
    for(int i=1;i<n;i++) cin>>v[i]>>w[i]>>s[i];

    // base case
    f[0][..]=0;
    f[..][0]=0;

    for(int i=1; i<=n; i++){
        for(int j=1; j<=m;j++){
          for(int k=0; k*w[i]<=j&&k<=s[i];k++){
            // k>=0开始 选0件、1件、2件
            // 与完全背包不同的地方在于多了条件:s<=s[i]
            f[i][j]=max(f[i-1][j-k*w[i]]+k*v[i]);
          }
        }
    }
    
    cout << f[n][m];
    return 0;
}

优化的主要思想一:
  1. 去掉[i]维度,此时只j维度需要倒序:因为 f[i][j]=max(f[i-1][j-k*v[i]]+k*w[i]) (0=<k<=p),类似0-1背包

代码如下:

// 多重背包 初级优化
// 这个优化版本还不能去掉k
// 因为你要考虑到物品的个数并不是无限
// 第一维:选择的物品循环
// 第二维:背包容量循环
// 第三维:选择的策略

#include <iostream>
#include <algorithm>

const int MAXN = 1005;
int w[MAXN];       // 重量
int v[MAXN];       // 价值
int s[MAXN];       // 个数
int f[MAXN][MAXN]; // f[i][j], j重量下前i个物品的最大价值
int main()
{
  int n, m;
  cin >> n >> m;
  for (int i = 1; i < n; i++)
    cin >> v[i] >> w[i] >> s[i];

  for (int i = 1; i <= n; i++)
  {
    // 同0-1背包的倒序
    for (int j = m; j >= w[i]; j++)
    {
      for (int k = 0; k * w[i] <= j && k <= s[i]; k++)
      {
        // k>=0开始 选0件、1件、2件
        // 与完全背包不同的地方在于多了条件:s<=s[i]
        //f[i][j]=max(f[i-1][j-k*w[i]]+k*v[i]);
        f[j] = max(f[j - k * w[i]] + k * v[i]); // 由于f[i-1][j-k*w[i]]->f[j-k*w[i]]所以要j倒序
      }
    }
  }

  cout << f[n][m];
  return 0;
}

在这里插入图片描述

优化的主要思想二:

物品的数量,和每个物品的种类数增加,之前的优化一算法已经过不了这个题目

  1. 二进制拆分来优化,第i件物品比方有8件,我们选0/1/2/3/4/5/6/7/8件可以看成8件物品选或者不选,那么就是8个0-1背包问题了,这样拆复杂度还比较高
    我们可以用二进制来拆,就是2^0, 2^1, 2^2 log8=3上取整这三个组件作为基类
    选0/1/2/3/4/5/6/7/8件可以看成8件物品选或者不选,可以由三个基类构成:
    例如:0=0, 1=2^0, 2=2^1, 3=2^1+2^0 4=2^1+2^1
    所以我们把这三个积累的0-1背包问题解出来选或者不选,8种背包再由这三类基础背包组成,就可以减少复杂度
# 关于拆分的代码:对于某一个种类的A吉他,数量有s个,二进制拆分也就数将s拆分
for(int k=1; k<=s; k*=2){
	s-=k;
	goods.push_back({v*k, w*k});
}
if(s>0){
	goods.push_back({v*s, w*s});
}
// 然后再以goods中的good为单位选或者不选,就可以组合成0~8的各种选择了,如果是A吉他正好有8把

整个二进制优化代码如下:

#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;
const int N=2010;
int f[N],n,m;

struct good{
	int w,v;
};

int main(){
	cin>>n>>m;
	vector<good> goods;
	//good tmp;
	
	// 二进制处理
	for(int i=1; i<=n; i++){
		int v,w,s; // 某一种商品的体积,价值,数量
		// 对某一种商品进行二进制拆分
		for(int k=1; k<=s; k*=2){
			s-=k;
			goods.push_back({k*w, k*v}); // 把每个基本类看成一个商品,所以要价格,体积要乘以k
		}
		if(s>0) goods.push_back({s*w, s*v});
	}

	// 二进制拆分后,每种商品拆成基本类,每个基本类看成一种商品,把所有商品的基本类组合起来goos,选或者不选每个基本类,转化成了0~1背包问题:选或者是不选每个基本类
	// 第一层循环,物品类的选择
	// 第一层选择的物品循环
  	// 第二层背包容量循环
  	// 第三层选择的策略
	for(auto i:goods){
		for(int j=m; j>=i.v;j--){
			f[j]=max(f[j], f[j-i.v]+i.w); // i.v~v[i] i.w~w[i] 
		}
	}
	cout << f[m] << endl;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值