01背包详解,DP思维的转换

这是HDU的2602题:


Problem Description
Many years ago , in Teddy’s hometown there was a man who was called “Bone Collector”. This man like to collect varies of bones , such as dog’s , cow’s , also he went to the grave …
The bone collector had a big bag with a volume of V ,and along his trip of collecting there are a lot of bones , obviously , different bone has different value and different volume, now given the each bone’s value along his trip , can you calculate out the maximum of the total value the bone collector can get ?




 


Input
The first line contain a integer T , the number of cases.
Followed by T cases , each case three lines , the first line contain two integer N , V, (N <= 1000 , V <= 1000 )representing the number of bones and the volume of his bag. And the second line contain N integers representing the value of each bone. The third line contain N integers representing the volume of each bone.
 


Output
One integer per line representing the maximum of the total value (this number will be less than 231).
 


Sample Input
1
5 10
1 2 3 4 5
5 4 3 2 1
 


Sample Output
14






先用DFS来解下题目,搜索很好写。。

#include<iostream>
using namespace std;
int T,d[1001],v[1001];

int dfsvalue(int i,int V)
{
	//考虑递归先考虑其结束条件(出口)。。
	//这里的结束条件是:
	//1.所有的东西都已经装完啦!
	if(i==0)return 0;
	//2.装i的话,背包的空间不够啦,那么装个i-1试试?
	if(v[i]>V)
	{
		return dfsvalue(i-1,V);
	}
	else
	{
		//这里有两种情况。
		//1.放第i个物品的情况。
		int get_i=dfsvalue(i-1,V-v[i])+d[i];//前置条件为取第i个物品,继续往下枚举
		//2.不放第i个物品的情况。
		int noget_i=dfsvalue(i-1,V);//前置条件为不取第i个物品,继续往下枚举
		//由于取与不取第i个物品,给剩余物品留下的空间不同,所以最后造成的最大价值也会不同,所以要比较一下两种情况所能获得的最大价值
		return get_i>noget_i?get_i:noget_i;//返回其中最大值。
		
		//好吧,这方法是挺暴力的。。
	}	
}

	int main()
	{
		scanf("%d",&T);
		while(T--)
		{
			//统统初始化
			memset(save_d_get,0,sizeof(save_d_get));
			memset(save_d_noget,0,sizeof(save_d_noget));
			int N,V;
			scanf("%d%d",&N,&V);
			for(int i=1;i<=N;i++)
				scanf("%d",&d[i]);
			for(int i=1;i<=N;i++)
				scanf("%d",&v[i]);
			printf("%d\n",dfsvalue(N,V));
		}
		return 1;
	}
	



仔细考虑搜索的方式,所用的是枚举,时间复杂度是O(2^n),对于大量数据而言,很容易超时,所以需要一定的优化。好吧,这是HDU的2602题,他已经超时了- -|||
一般而言,题目对时间的要求较为严格,而对空间的要求比较宽松,那么。。能不能把一部分的时间转换到空间上来呢?

再仔细分析一下这个dfs,思考两分钟后。。

咦,好像有什么重复运算了。。


好的,接下来优化一下。。

#include<iostream>
using namespace std;

int T,d[1001],v[1001],dsum[1001]={0};//加上剪枝
int save_d_get[1001],save_d_noget[1001];//记录两种情况,取或不取,并且初始化。

int dfsvalue(int i,int V)
{
	//考虑递归先考虑其结束条件(出口)。。
	//这里的结束条件是:
	//1.所有的东西都已经装完啦!
	if(i==0)return 0;
	//2.装i的话,背包的空间不够啦,那么装个i-1试试?
	if(v[i]>V)
	{
		if(save_d_noget[i])
			return save_d_noget[i];
		else
			return save_d_noget[i]=dfsvalue(i-1,V);
	}
	else
	{
		//这里有两种情况。
		//1.放第i个物品的情况。
		int get_i,noget_i;
		if(save_d_get[i])get_i=save_d_get[i];
		else 
			{
			save_d_get[i]=dfsvalue(i-1,V-v[i])+d[i];//前置条件为取第i个物品,继续往下枚举
			get_i=save_d_get[i];
			}
		//2.不放第i个物品的情况。
		if(save_d_noget[i])noget_i=save_d_get[i];
		else
			{
			if(get_i>dsum[i-1])return get_i;  //不管有没有用,先剪一刀。。
			save_d_noget[i]=dfsvalue(i-1,V);//前置条件为不取第i个物品,继续往下枚举
			noget_i=save_d_noget[i];
			}
		//由于取与不取第i个物品,给剩余物品留下的空间不同,所以最后造成的最大价值也会不同,所以要比较一下两种情况所能获得的最大价值
		return get_i>noget_i?get_i:noget_i;//返回其中最大值。
		
	}	
}

	int main()
	{
		scanf("%d",&T);
		while(T--)
		{
			//统统初始化
			memset(save_d_get,0,sizeof(save_d_get));
			memset(save_d_noget,0,sizeof(save_d_noget));
			int N,V;
			scanf("%d%d",&N,&V);
			for(int i=1;i<=N;i++)
			{
				scanf("%d",&d[i]);
				dsum[i]+=dsum[i-1]+d[i];
			}
			for(int i=1;i<=N;i++)
				scanf("%d",&v[i]);
			printf("%d\n",dfsvalue(N,V));
		}
		return 1;
	}

优化完毕(记忆化搜索加剪枝),丢过去看看。。  卧槽,wa了(不让用搜索么)。。求大神赐教。。

好吧,以上DFS失败了。。换DP,DP边学边写吧。。

动态规划具有两个特征:
1.重叠子问题。
2.最优化子结构。

第一个特征重叠子问题,之前几天的递推练习中已经熟悉,然而对最优化子结构不是很熟悉。

最优化子结构:
{1)最优子结构,这是采取动态规划策略解最优化问题后要做的第一步。所谓最优化子结构是说若问题的一个最优解中包含了子问题的最优解,则该问题具有最优子结构。这个是我们采取动态规划的一个充分条件(当然这个条件也满足贪婪策略),问题出现这个条件就可以考虑采取动态规划。


        一般要考虑的因素是:


                1.1)最优解里需要解决的子问题数量有多少?


                1.2)在判断使用那些子问题时需要进行多少选择?
}(摘自internet)

父问题的最优解依靠于一些子问题的最优解。

数塔问题:


到达x,y层所能拥有的最大数字取决于max(“达到x+1,y层的最大数字”,“达到x+1,y+1层的最大数字”)。也就是说父问题转化成了一个子问题,到达x,y层所能拥有的最大数字--->到达到x+1,y层的最大数字和达到x+1,y+1层的最大数字哪个大,状态发生了转移。
很容易得出一个等式,f[x][y]=max(f[x+1][y],f[x+1][y+1])+map[x][y],得到这个关系式后,写代码就很容易了。

	
#include<stdio.h>
int max(int x,int y)
{
		if(x>y)
			return x;
		return y;
}
int main()
{
	int n,m,ta[101][101]={0},dp[102][102]={0};
	
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=i;j++)
			scanf("%d",&ta[i][j]);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			dp[i][j]=ta[i][j]+max(dp[i+1][j],dp[i][j+1]);
	printf("%d\n",dp[n][m]);

}



好的,一次AC。。不错的开始。。




从数塔问题推广至01背包。


分析:
01背包的最终问题是在一定的背包空间V里用不同大小v[i]的石头来填充,石头具有不同的价值,且石头的大小和价值不变,
若减少背包空间,相应的最高价值也会改变,若减少石头的数目,相应的最高价值也会改变,那么决定最高价值的有两个变量n和v。
最高价值用dp[n][v]表示,代表在v空间里面,用数目为n的石头去试着填充所能获得的最高价值。决定是否用第n个石头去填充的有两个
条件,1.背包空间够吗。2.填充后是否会获得最高价值。if(v[n]<=V)max(dp[n-1][V],dp[n-1][V-v[n]]+d[n])
现在已经把状态转移的方程式写出来了。
若n-1个石头在V空间里能获得的最高价值已经确定了,那么决定第n个石头去试着填充能获得的最高价值的是--->max(第n-1个石头在V空间里能获得的最高价值(不放第n个石头),第n-1个石头在V-v[n]空间里的最高价值加上的第n石头的价值(放第n个石头))
通过dp[n][v],这是由两个变量组成的,那么通过两重循环可以轻易的将其表达出来。

#include<iostream>
using namespace std;
	
int d[1001]={0},v[1001]={0};
int dphighestvalue[1001][1001];
//变量定义在全局,用堆空间,否则定义在main内会使用栈空间,导致栈溢出!
int max(int x,int y)
{
		if(x>y)
			return x;
		return y;
}
int main()
{

		int T;
		memset(dphighestvalue,0,sizeof(dphighestvalue));
		scanf("%d",&T);
				while(T--)
		{
			int N,V;
			scanf("%d%d",&N,&V);
			for(int i=1;i<=N;i++)
				scanf("%d",&d[i]);
			for(int i=1;i<=N;i++)
				scanf("%d",&v[i]);
			for(int i=1;i<=N;i++)
				for(int j=0;j<=V;j++)
					if(v[i]<=j)dphighestvalue[i][j]=max(dphighestvalue[i-1][j],dphighestvalue[i-1][j-v[i]]+d[i]);
					else dphighestvalue[i][j]=dphighestvalue[i-1][j];			
			printf("%d\n",dphighestvalue[N][V]);
		}
		return 1;
}


好的,丢过去看看,嗯。。AC了,然后再看看能不能优化一下。。观察一下那个二维数组,好像有些地方记录过就不再使用了,比如说当我记录过dp[i-1]这一行数组后,
只有dp[i]这一行数组会用到它,当n=i+2时,dp[i+2]只会使用dp[i+1]这行数组,也就是说,每次只需要两个一维数组就可以了。

#include<iostream>
using namespace std;

int d[1001]={0},v[1001]={0};
int flag=1; //用来交换数组
int dphighestvalue[2][1001];//优化成两条一维数组。
//变量定义在全局,用堆空间,否则定义在main内会使用栈空间,导致栈溢出!
int main()
{

		int T;
		scanf("%d",&T);
		while(T--)
		{
			memset(dphighestvalue,0,sizeof(dphighestvalue));
			int N,V;
			scanf("%d%d",&N,&V);
			for(int i=1;i<=N;i++)
				scanf("%d",&d[i]);
			for(int i=1;i<=N;i++)
				scanf("%d",&v[i]);

			for(int i=1;i<=N;i++)
			{
					for(int j=0;j<=V;j++)
						if(v[i]<=j)dphighestvalue[flag][j]=max(dphighestvalue[!flag][j],dphighestvalue[!flag][j-v[i]]+d[i]);
						else dphighestvalue[flag][j]=dphighestvalue[!flag][j];
				flag=!flag;
			}
			printf("%d\n",dphighestvalue[!flag][V]);
		}
		return 1;
}

好像还能再优化一下。。。把它改成只有一条一维数组怎么样?


#include<iostream>
using namespace std;

int d[1001]={0},v[1001]={0};
int dphighestvalue[1001];//改成一条一维数组!
//变量定义在全局,用堆空间,否则定义在main内会使用栈空间,导致栈溢出!
int main()
{

		int T;
		scanf("%d",&T);
		while(T--)
		{
			memset(dphighestvalue,0,sizeof(dphighestvalue));
			int N,V;
			scanf("%d%d",&N,&V);
			for(int i=1;i<=N;i++)
				scanf("%d",&d[i]);
			for(int i=1;i<=N;i++)
				scanf("%d",&v[i]);

			for(int i=1;i<=N;i++)
				for(int j=V;j>=0;j--) //这边要修改一下,顺序应该是从大到小,不然原有的数据会被覆盖,如果是两个一维当然没问题啦
					if(v[i]<=j)dphighestvalue[j]=max(dphighestvalue[j],dphighestvalue[j-v[i]]+d[i]);
			printf("%d\n",dphighestvalue[V]);
		}
		return 1;
}

好吧,这是渣渣的一次DP。。。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值