动态规划之背包问题

背包问题(Knapsack problem)是一种组合优化的NP完全问题。问题可以描述为:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。问题的名称来源于如何选择最合适的物品放置于给定背包中。相似问题经常出现在商业、组合数学,计算复杂性理论、密码学和应用数学等领域中。也可以将背包问题描述为决定性问题,即在总重量不超过W的前提下,总价值是否能达到V?

背包问题一般分为以下几类:

1.01背包问题

2.完全背包问题

3.多重背包问题

4.分组背包问题

一、01背包问题

01背包问题的限定条件是每种物品只能买一个,给定每个物体的重量v和价值w,问n个物体在总重量不超过m的情况下最大可买到的价值。

我们用数组f[i][j]表示只选前i个物体,并且总体积不超过j的最大价值,01背包问题中,每一个物品只有选与不选两种状态,所以f[i][j]就等于不选这个物品与选了这个物品的最大值。这两种状态前者可表示为f[i-1][j],后者可表示为f[i-1][j-v[i]]+w[i];

#include<bits/stdc++.h>
using namespace std;
const int N=1000;
int f[N][N];
int v[N],w[N];
using namespace std;
int main()
{
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	cin>>v[i]>>w[i];
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<m;j++)
		{
		f[i][j]=f[i-1][j];
		if(j>v[i])
		f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);
	    }
	}
	cout<<f[n][m]<<endl;
}

01背包问题还可优化为1维数组,进行n次循环,第i次循环完f[j]表示的时只选前i个物品,总重量不超过j的最大价值。 

但是当我们枚举重量j时,是从更小的重量转移过来,或者说是从第i-1次循环的状态转移过来,如果重量从小到大枚举f[j-v[i]]已经被更新过,放在二维数组里此时的f[j-v[i]]表示的是f[i][j-v[i]]而不是 f[i-1][j-v[i]]。所以我们将重量从大到小枚举。

#include<bits/stdc++.h>
using namespace std;
const int N=1000;
int f[N];
int v[N],w[N];
using namespace std;
int main()
{
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	cin>>v[i]>>w[i];
	for(int i=1;i<=n;i++)
	{
		for(int j=m;j>=v[i];j--)
		f[j]=max(f[j],f[j-v[i]]+w[i]);
	}
	cout<<f[m]<<endl;
}

二、完全背包问题

完全背包问题的限定条件是每种物品可以买无限个,给定每个物体的重量v和价值w,问n个物体在总重量不超过m的情况下最大可买到的价值。

01背包问题其实就是完全背包问题只买0个或者1个的情形。

朴素代码和01挺像的

#include<bits/stdc++.h>
using namespace std;
const int N=1000;
int f[N][N];
int v[N],w[N];
using namespace std;
int main()
{
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	cin>>v[i]>>w[i];
	for(int i=1;i<=n;i++)
	{
	   for(int j=0;j<=m;j++)
	   {
	   	for(int k=0;k*v[i]<=j;k++)
	   	f[i][j]=max(f[i][j],f[i-1][j-v[i]*k]+w[i]*k);
	   }
	}
	cout<<f[n][m]<<endl;
}

那么完全背包问题怎么进行优化呢?

因为物品是无限个,所以f[i][j]与f[i][j-v],都是选0个,选1个,直到k个使得剩下重量不足以再选一个。所以从图中可知,f[i][j]为f[i-1][j]与f[i][j-v]+w的最大值。

#include<bits/stdc++.h>
using namespace std;
const int N=1000;
int f[N];
int v[N],w[N];
using namespace std;
int main()
{
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	cin>>v[i]>>w[i];
	for(int i=1;i<=n;i++)
	{
		for(int j=v[i];j<=m;j++)
		f[j]=max(f[j],f[j-v[i]]+w[i]);
	}
	cout<<f[m]<<endl;
}

01背包问题和完全背包问题的代码区别仅是前者j从大到小枚举,后者从小到大枚举。原因是

01背包问题f[i][j]是从f[-1]的状态转移过来,完全背包问题f[i][j]为f[i-1][j]与f[i][j-v]+w的最大值。是从

第f[i]转移过来,从小到大枚举保证了f[j-v[i]]是第i层的状态而不是第i-1层的状态。

三、多重背包问题

多重背包问题的限定条件是每种物品的个数是有限个,给定每个物体的重量v和价值w,问n个物体在总重量不超过m的情况下最大可买到的价值。

朴素代码在完全背包问题上加个限定条件即可。

#include<bits/stdc++.h>
using namespace std;
const int N=1000;
int f[N][N];
int v[N],w[N],s[N];
using namespace std;
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++)
	{
	   for(int j=0;j<=m;j++)
	   {
	   	for(int k=0;k*v[i]<=j&&k<=s[i];k++)//买个物品数量不能超过k个 
	   	f[i][j]=max(f[i][j],f[i-1][j-v[i]*k]+w[i]*k);
	   }
	}
	cout<<f[n][m]<<endl;
}

多重背包问题的优化方式是通过二进制优化,将多重背包问题转化为01背包问题。

为什么不能像完全背包问题那样优化呢?

 

如果物品的数量超过了最大可买数量,还是可以像多重背包问题那样优化的,但是如果比最大可买数量少,当买完所有东西,对于f[i][j]的最后一个转移状态是f[i][j-s[i]*v[i]]+w[i]*s[i], 对于f[i][j-v]的最后一个转移状态是f[i][j-(s[i]+1)*v[i]]+w[i]*s[i]+1。所以并不能像完全背包问题那样优化。

多重背包问题通过二进制优化转化为01背包问题。

例如,一个物品有1023个,我们将其分为若个组,每一组算一个新的物品,第一个物品的重量为v[i],价值为w[i],第二个物品重量为v[i]*2,价值w[i]*2,每组都是前一组的2倍,最后不够的再一组。

这样对于1023中选取任意个物品,一定是任意组之和。所以对于原来的1023个物品选几个,就转化为这新的几组(个)是每一个组(个)是选还是不选。

#include<bits/stdc++.h>
using namespace std;
const int N=100010;
int v[N],w[N],f[N]; 
int cnt=0,n,m;
int main()
{
	cin>>n>>m;
	for(int i=0;i<n;i++)
	{
		int a,b,s,k=1;
		cin>>a>>b>>s;
		while(k<=s)
		{
			cnt++;
			v[cnt]=a*k;
			w[cnt]=b*k;
			s-=k;
			k=k*2;
		}
		if(s>0)
		{
			cnt++;
			v[cnt]=a*s;
			w[cnt]=b*s;
		}
	}
	for(int i=1;i<=cnt;i++)
	{
		for(int j=m;j>=v[i];j--)
		f[j]=max(f[j],f[j-v[i]]+w[i]);
	}
	cout<<f[m]<<endl;
}

 四、分组背包问题

分组背包问题的限定条件是n组物品,每组有若干个,给定每个物体的重量v和价值w,问n组每个组只能买一个在总重量不超过m的情况下最大可买到的价值。

#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int f[N],s[N];
int v[N][N],w[N][N];
int main()
{
	int n,m; 
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		cin>>s[i];
		for(int j=1;j<=s[i];j++)
		cin>>v[i][j]>>w[i][j];
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=m;j>=0;j--)
		{
			for(int k=1;k<=s[i];k++)
			if(v[i][k]<=j)
			f[j]=max(f[j],f[j-v[i][k]]+w[i][k]);
		}
	}
	cout<<f[m]<<endl;
}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
动态规划是一种将大问题分解为小问题进行解决的方法,背包问题动态规划中最经典的题型之一。背包问题分为三类:01背包、完全背包和多重背包。其中,01背包问题是最经典的背包问题,也是动态规划的入门级必学算法。\[1\] 动态规划解决背包问题的核心思想是将问题分解为若干个小问题,先求解子问题,然后从子问题的解得到原问题的解。与分治法不同的是,动态规划适用于有重叠子问题的情况,即下一阶段的求解是建立在上一阶段的解的基础上进行进一步求解。通过填表的方式,逐步推进,最终得到最优解。\[2\] 多重背包问题介于01背包和完全背包之间,可以将其转化为01背包或完全背包问题来求解。对于某种物品,如果其数量乘以单位体积大于背包总容量,那么该物品与背包之间是完全背包问题。而对于某种物品,可以将其数量视为不同的物品,然后按照01背包问题进行处理。这样的转化可以在数据范围较小时适用,但在数量较大时可能会导致超时。因此,可以采用更精炼的划分方案,如二进制拆分,来减少物品分类的组数,从而优化算法的效率。\[3\] 总结来说,动态规划是一种解决背包问题的有效方法,通过将大问题分解为小问题,并利用子问题的解来求解原问题,可以得到背包的最优解。 #### 引用[.reference_title] - *1* *3* [【算法与数据结构】—— 动态规划背包问题](https://blog.csdn.net/the_ZED/article/details/104882665)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [动态规划算法解决经典背包问题](https://blog.csdn.net/m0_52110974/article/details/120122061)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值