C++ 多重背包+二进制优化(比完全背包难1/inf左右的DP)

本文详细讲解了多重背包问题的定义及解决方法,包括状态转移方程的推导、原始算法实现及其时间复杂度分析,最后介绍了通过二进制优化将时间复杂度降低的技术。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

=洛谷上找不到题了,没有传送门,不过凑合着看看吧

如果你还没有学过背包问题的话,推荐你看一下我的这一篇博客:

C++ 0/1背包(比数字三角形难1/inf左右的DP)_jinjiayang的博客-CSDN博客

题面

好不容易从洛谷上找到的题目,感谢

@SkeletonKing233

题目描述:
给出N个物品(一个物品可以选择0~a个),背包最大承重为M,每个物品有一个重量w,一个价值v,一个个数a。如何选择才能在重量不超过M的情况下,使选择的物品的价值总和最大。

输入格式:
第一行:是两个正整数N,M。
第2 ~ (n + 1)行:每行三个正整数w,v和a。

输出格式:
一个整数,为最大价值总和是多少。

输入样例:

4 20
9 3 3
9 5 1
4 9 2
1 8 3
1
2
3
4
5
输出样例:

47

题解

如果你看过0/1背包的话,下面所述的对你而言应该还是比较轻松的

判断

很明显,这一题同样满足最优子结构和无后效性原理,可使用动态规划解决

状态转移方程

在0/1背包中我们知道,状态转移方程是

dp[j] = max(dp[j-w[i]] + v[i], dp[j]);

那么在多重背包中,其实和0/1背包的状态转移方程是类似的,

但是!

我们发现,一个物品可以带的次数是有限制的,因此我们在创建一个变量k,代表我们选择了这个物品k次,状态转移方程就是

dp[j] = max(dp[j], dp[j-k*wgt[i]] + k*vls[i]);

 其含义就不再多介绍了

代码

#include<bits/stdc++.h>
using namespace std;

int wgt[102], vls[102], sum[102], dp[102];
int n, c;
int main()
{
	cin >> n >> c;
	for(int i=1; i<=n; i++) cin >> wgt[i] >> vls[i] >> sum[i];
	for(int i=1; i<=n; i++) for(int j=c; j>=wgt[i]; j--) for (int k=1; k<=sum[i] && k*wgt[i]<=j; k++)
	{
        dp[j] = max(dp[j], dp[j-k*wgt[i]] + k*vls[i]);
    }
	cout << dp[c];
}

二进制优化

此刻我们发现,我们的复杂度达到了惊人的O(n^3),也就是立方阶,因此我们考虑对其进行优化

转化

我们发现,买3件物品等于买1件+2件物品,买5件物品等于买1件+4件物品,

1=1, 2=2, 3=1+2, 4=4, 5=1+4, 6=2+4, 7=1+2+4,  8=8,  9=1+8,  10=2+8...        

一个正整数肯定写成若干个二的幂相加的表示形式,即任何正整数皆可写成一个二进制数

因此我们可以把一个商品拆成1,2,4,8,16...n个商品,并将其转化为0/1背包进行求解!

最终优化代码

#include <bits/stdc++.h>
using namespace std;
 
const int maxn=25000;
int N,V;
int w[maxn],v[maxn];
int dp[maxn];
 
int main() {
	cin>>N>>V;
	int cnt=0;
	for(int i=1; i<=N; i++) {
		int wi,vi,s;
		cin>>wi>>vi>>s;
		
		int k=1;		
		while(k<=s) {
			cnt++;
			w[cnt]=wi*k;
			v[cnt]=vi*k;
			s-=k;
			k*=2;
		}
		
		if(s>0) {
			cnt++;
			w[cnt]=wi*s;
			v[cnt]=vi*s;
		}
	}
 
	N=cnt;
	for(int i=1; i<=N; i++) {
		for(int j=V; j>=w[i]; j--)
			dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
	}
 
	cout<<dp[V]<<endl;
	return 0;
 
}
 

不想敲键盘了,感谢@hnjzsyjyj的代码!

至此我们成功把n^3的复杂度降低为了n^2的!

二进制优化核心部分

int k=1;		
while(k<=s) {
	cnt++;
	w[cnt]=wi*k;
	v[cnt]=vi*k;
	s-=k;
	k*=2;
}
		
if(s>0) {
	cnt++;
	w[cnt]=wi*s;
	v[cnt]=vi*s;
}

time to 点赞

看完后,别忘了

点赞!

收藏!

Thanks……

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

嘉定世外的JinJiayang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值