C++多重背包模板

我是蒟蒻!!!

再次建议配合前文阅读:C++完全背包模板-CSDN博客

多重背包题解【模板】: 

题目描述

有 N 种物品和一个容量是 V 的背包。
第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。

输出最大价值。

输入格式

第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。

接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。

输出格式

输出一个整数,表示最大价值。

数据范围

0<N≤1000
0<V≤2000
0<vi,wi,si≤2000

输入#1

4 5
1 2 3
2 4 1
3 4 3
4 5 2

输出#1

10

题目解释

题目看上去为01背包和完全背包的结合,实则难度更大,优化方式也不同。

照例,先写出无优化的代码

多重背包无优化

核心代码如下:

for (i=1;i<=n;i++)
{
	for (k=1;k<=s[i];k++)
	{
		for (j=k*v[i];j<=V;j++)
		{
			f[i][j]=max(f[i-1][j],max(f[i-1][j-k*v[i]]+k*w[i],f[i][j-1]));
		}
	}
}

写法与完全背包大致相同,为不选,选,传递j-1的最大值,但是此时k有s[i]的限制

Ac代码如下: 

#include <iostream>
#include <cstdio>
using namespace std;
int f[1005][1005];
int v[1005],w[1005],s[1005];
int n,V;
int main ()
{
	int i,j,k;
	scanf("%d%d",&n,&V);
	for (i=1;i<=n;i++) scanf("%d%d%d",&v[i],&w[i],&s[i]);
	for (i=1;i<=n;i++)
	{
		for (k=1;k<=s[i];k++)
		{
			for (j=k*v[i];j<=V;j++)
			{
				f[i][j]=max(f[i-1][j],max(f[i-1][j-k*v[i]]+k*w[i],f[i][j-1]));
			}
		}
	}
	printf("%d",f[n][V]);
	return 0;
}

多重背包二进制优化:

我们可以将任何一个数写成由1和0组成的二进制数,也就是说,我们可以用二进制数组成一个数,如,我们可以用1(0001)2(0010)4 (0100)组成7(0111),那么我们是不是可以将某个物品的使用次数s[i]拆分成由二进制从小到大累加的数?

假如此时s[i]=7,那么我们可以拆成1+2+4,再将物品使用次数“捆绑”为一个新的物品,把1个物品看为体积为v[i],价值为w[i],把2个物品看为体积为2*v[i],价值为2*w[i],把4个物品看为体积为4*v[i],价值为4*w[i],再对它们进行完全背包的优化。

那如果某个数不能恰好由2^n组成的数呢,8(1000)不能从小到大被2^n的数累加,那么我们就将剩余的数再当作一个物品进行操作,我们将8拆成1 (0001)2 (0010)4 (0100)1(0001),此时就可以进行完全动态规划了

核心代码如下:

x=v[i]

y=v[i]

z=s[i]

cnt=0
for (i=1;i<=n;i++)
{
	cin>>x>>y>>z;
	for (j=1;j<=z;j*=2)
	{
		v[++cnt]=x*j;
		w[cnt]=y*j;
		z-=j;
	}
	if (z) 
	v[++cnt]=x*z,
	w[cnt]=y*z;
}
n=cnt;

j循环2的倍数,将s[i]减去j,判断是否能再减去一个2的倍数

如果有剩余,将剩余的数捆绑为新的物品

注意:拆分后物品的总数应该为cnt而不是n

for (i=1;i<=n;i++)
{
	for (j=m;j>=v[i];j--)
	{
		a[j]=max(a[j],a[j-v[i]]+w[i]);
	}
}

倒序循环,因为我们已经将物品捆绑,不需要再重复累加,运用本轮的值,进行递推,与01背包相同

Ac代码如下:

#include <bits/stdc++.h>
using namespace std;
int v[2005],w[2005],a[2005];
int n,m,cnt;
int main ()
{
	int i,j;
	int x,y,z;
	cin>>n>>m;
	for (i=1;i<=n;i++)
	{
		cin>>x>>y>>z;
		for (j=1;j<=z;j*=2)
		{
			v[++cnt]=x*j;
			w[cnt]=y*j;
			z-=j;
		}
		if (z) 
		v[++cnt]=x*z,
		w[cnt]=y*z;
	}
	n=cnt;
	for (i=1;i<=n;i++)
	{
		for (j=m;j>=v[i];j--)
		{
			a[j]=max(a[j],a[j-v[i]]+w[i]);
		}
	}
	int Max=-1;
	for (i=1;i<=m;i++) Max=max(Max,a[i]);
	cout<<Max;
	return 0;
}

成功Ac,完结撒花!

测试参考题目:P1757 通天之分组背包 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

(注意将输入输出,数组大小进行修改)

如有不解或优化,可在评论区留言

  • 15
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值