动态规划的01背包

动态规划的01背包

01背包

特点是:每种物品仅有一件,可以选择放或不
放。
用子问题定义状态:即 F[i,v] 表示前 i 件物品恰放入一个容量为 v 的背包可
以获得的最大价值。则其状态转移方程便是:
F[i,v] = max {F[i − 1,v],F[i − 1,v − C i ] + W i }

杭电2602题
传送门:杭电2602题
坑点:
1
5 0
2 4 1 5 1
0 0 1 0 0
答案是12
过了题目数据和这组数据,基本就能AC了~

1、dp数组为二维的,没有进行压缩(但好理解)

基本思路:“将前 i 件物品放入容量为 v 的背包
中”这个子问题,若只考虑第 i 件物品的策略(放或不放),那么就可以转化
为一个只和前 i − 1 件物品相关的问题。如果不放第 i 件物品,那么问题就转化
为“前 i − 1 件物品放入容量为 v 的背包中”,价值为 F[i − 1,v] ;如果放第 i 件物
品,那么问题就转化为“前 i − 1 件物品放入剩下的容量为 v − C i 的背包中”,
此时能获得的最大价值就是 F[i − 1,v − C i ] 再加上通过放入第 i 件物品获得的

伪代码如下:
F[0,0…V ] ← 0
for i ← 1 to N
for v ← C i to V
F[i,v] ← max {F[i − 1,v],F[i − 1,v − C i ] + W i }

#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
int n,m;  //n为物品的个数,m为书包的承重 
int value[1005];   //value为各物品的价值 
int weight[1005];  //weight为个物品的重量
int dp[1005][1005];  //dp数组 
int bag()  //0-1背包 
{
	memset(dp,0,sizeof(dp));   //初始化dp数组 
	for(int i=1;i<=n;i++)   //遍历所有物品 
	{
		for(int j=0;j<=m;j++)  //遍历所有重量 
		{
			if(weight[i]>j)  dp[i][j]=dp[i-1][j];   
			//当前物品的重量超过背包的承载重量,则不加入,依旧是上一次的价值 
			else  if(dp[i-1][j]>dp[i-1][j-weight[i]]+value[i]) 
			        dp[i][j]=dp[i-1][j];
			else dp[i][j]=dp[i-1][j-weight[i]]+value[i];
			//比较加入物品和不加入物品,取最大值 
		}
	}
	return dp[n][m];  //返回结果
}
int main()
{
	int result,t;
	cin>>t;
	while(t--)  //输入物品数量和背包的承载重量 
	{
		cin>>n>>m;
		for(int i=1;i<=n;i++)  //输入物品的价值 
	        cin>>value[i];
	    for(int i=1;i<=n;i++)  //输入物品的重量 
	        cin>>weight[i];
	    result=bag(); 
	    cout<<result<<endl;
	}
	return 0;
}

2、优化空间复杂度,dp数组为一维

基本思路:先考虑上面讲的基本思路如何实现,肯定是有一个主循环 i ← 1…N ,每
次算出来二维数组 F[i,0…V ] 的所有值。那么,如果只用一个数组 F[0…V ] ,
能不能保证第 i 次循环结束后 F[v] 中表示的就是我们定义的状态 F[i,v] 呢? F[i,v] 是
由 F[i−1,v] 和 F[i−1,v −C i ] 两个子问题递推而来,能否保证在推 F[i,v] 时(也
即在第 i 次主循环中推 F[v] 时)能够取用 F[i − 1,v] 和 F[i − 1,v − C i ] 的值呢?
事实上,**这要求在每次主循环中我们以 v ← V …0 的递减顺序计算 F[v] ,
这样才能保证计算 F[v] 时 F[v − C i ] 保存的是状态 F[i − 1,v − C i] 的值。**伪代码如
下:
F[0…V ]←0
for i ← 1 to N
for v ← V to C i
F[v] ← max {F[v],F[v − C i ] + W i }

由dp[i][w]=max(dp[i-1][w-w[i]]+c[i], dp[i-1][w]可以看出要求第i组的dp [w]需要的是第i-1组的dp[w-w[i]]+c[i]和dp[w],即求dp[w]时需要当前的数据和w位置前的数据,因此求第i组的时候需要逆序求解,这样可以保证求dp[w]时所需的两个数据都是上一组的,而且当w<w[i]时dp[w]=dp[w],

因此只要循环M…w[i]即可

#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
int n,m;  //n为物品的个数,m为书包的承重 
int value[1050];   //value为各物品的价值 
int weight[1050];  //weight为个物品的重量
int dp[1050];  //dp数组 
int bag()  //0-1背包 
{
	memset(dp,0,sizeof(dp));  //初始化dp数组 
	for(int i=1;i<=n;i++)   //遍历每一个背包 
		for(int j=m;j>=weight[i];j--)   //逆序遍历每一个背包的重量 
		{   // 取最大值 
			if(dp[j]>dp[j-weight[i]]+value[i])
			  dp[j] = dp[j];
			else dp[j]=dp[j-weight[i]]+value[i];
		}		 
	return dp[m];  //返回重量m的最大价值 
}
int main()
{
	int result,t;
	cin>>t;  //输入例子数 
	while(t--)  
	{
		cin>>n>>m;
		for(int i=1;i<=n;i++)  
	        cin>>value[i];  //输入价值 
	    for(int i=1;i<=n;i++)  
	        cin>>weight[i];  //输入重量 
	    result=bag();   //得到结果 
	    cout<<result<<endl;
	}
	return 0;
}

3、初始化的细节问题

我们看到的求最优解的背包问题题目中,事实上有两种不太相同的问法。
有的题目要求“恰好装满背包”时的最优解,有的题目则并没有要求必须把背
包装满。一种区别这两种问法的实现方法是在初始化的时候有所不同。
如果是第一种问法,要求恰好装满背包,那么在初始化时除了 F[0] 为 0 ,其
它 F[1…V ] 均设为 −∞ ,这样就可以保证最终得到的 F[V ] 是一种恰好装满背包的
最优解。
如果并没有要求必须把背包装满,而是只希望价格尽量大,初始化时应该
将 F[0…V ] 全部设为 0 。
这是为什么呢?可以这样理解:初始化的 F 数组事实上就是在没有任何物
品可以放入背包时的合法状态。如果要求背包恰好装满,那么此时只有容量
为 0 的背包可以在什么也不装且价值为 0 的情况下被“恰好装满”,其它容量的
背包均没有合法的解,属于未定义的状态,应该被赋值为-∞了。如果背包并非
必须被装满,那么任何容量的背包都有一个合法解“什么都不装”,这个解的
价值为 0 ,所以初始时状态的值也就全部为 0 了。
这个小技巧完全可以推广到其它类型的背包问题。

猜你喜欢

转载自blog.csdn.net/qq_39905917/article/details/83515375