动态规划之买瓜子—C说算法系列

熟能生巧,大多数的解题步骤或者思路都可通过训练获得。将事情重复做,即使刚开始你感觉素手无错,一脸茫然,但是通过重复训练最终也能够游刃有余


题目来源:第十二届蓝桥杯青少年国赛C++中级组-编程题4

这是一道DP(动态规划)的题目,大家都知道,DP算法的效率极高,但是较难理解。在很多算法的题解中对 DP在该题中的应用思路  只简单提了几句。我们来看一道题,或许题解过程能让你对DP的理解更加深入一些。

题目内容:

校庆,采购瓜子。资金N(1<=N<=1000)M(1<=M<=30)种瓜子。问最多能采购多少千克的瓜子?比如N=80元,M=2种。第1种,每袋18元10千克;第2种,每袋30元20千克。

输入样例:

80 2

18 10

30 20

输出样例

50

提示:18+30+30=78元 10+20+20=50千克


分析:

这是完全背包问题,是一道模板题,是必须要掌握的(背代码也要背下来)那什么是完全背包问题呢?

完全背包问题:一般是指,有N件物品和一个能背重量为W的背包,第i件物品的重量为weight[i],价格为value[i]。每件物品有无限个(也就是可以放入背包多次),求怎样可以使背包物品价值总量最大

本道题我们完全可以套模板。资金N元(按照完全背包问题的解释,我们可以把N看成W),即有M种瓜子,有资金N元,求怎样可以使买的瓜子重量最多?

动态规划算法最重要的两点是:

1、确定dp数组和下标的含义        2、确定递推公式(选或者不选物品)

本题中的dp数组我们可以这样定义:int dp[50][1010]; 

dp[i][w]代表 - 前i个物品,在价值为w的情况下,最大的重量是dp[i][w]

因为M<=30,物品的数量不会大于30,所以dp的第一个维度,我们设置一个大于30的长度即可。资金W<=1000,dp的第二个维度代表资金,资金<=1000,所以第二个维度我们设置一个大于1000的长度即可。


该题中涉及到的数据结构有:

数据的输入

输入资金和瓜子种数

 

数据的处理

此步是最核心最关键的,我们定义了dp[i][w]数组,该数组的含义代表前i个物品,在价格为w的情况下,最大的重量是dp[i][w],对于本题的例子当N=80,M=2的情况下,最多能买50kg的瓜子。即求出dp[2][80]的值。

在dp中,要求出dp[i][j]的值,需要通过递推计算而来。

注意:要认真区别此处各种符号表示的含义。

对于每一种瓜子,都有选与不选两种选择。dp数组的初始化,当i=0或者w=0的情况下,dp[i][w]均为0,因为dp[0][w]代表前0件商品,在价格为w的情况下,最大的重量,既然没有商品,那么最大的重量肯定为0,dp[i][0]代表前i件商品,在资金为0下,最大的重量,既然没有资金,那么最大的重量肯定为0.

以资金=80,种类=2为例,有:

dp[0][0],dp[0][1],dp[0][2],dp[0][3],dp[0][4]……dp[0][80]的值均为0.

dp[0][0],dp[1][0],dp[2][0],dp[3][0],dp[4][0]……dp[50][0]的值均为0.

可通过嵌套for循环进行递推,利用for循环求出dp[i][w]

在当前资金j不够买第i种瓜子的情况下,dp[i][j]的值等于dp[i-1][j]的值,dp[i-1][j]代表前i-1种瓜子,在价格为j下,最大的重量为dp[i-1][j].

比如对于dp[1][16],在前1种瓜子下,当前资金是16元,但是根据题目意思,第1种瓜子的价格为18,所以dp[1][16]=dp[0]=16]=0千克,无法买瓜子,所以最大重量为0

再比如对于dp[1][18],在前1种瓜子下,当前资金是18元,第1种瓜子的价格刚好为18元,资金足够,根据代码dp[1][18]=max(dp[0][18],dp[1][0]+10)=10千克,所以最大重量为10千克

再比如对于dp[2][19],,在前2种瓜子下,当前资金是19元,当前第2种瓜子的价格为30元,资金不够,因此dp[i][j]=dp[i-1][j]=dp[2][19]=dp[1][19]=10千克

对于每一种瓜子,只有买和不买两种选择,对于前i种瓜子,在现有资金为j下,如果资金足够,我们可以买,也可以不买,那到底买不买呢?

买不买取决于:如果不买的重量比买了的重量更大,就不买,如果买了的重量比不买的重量更大,就买。本题例子的输入,不好解释dp[i][j]=max(dp[i-1][j],dp[i][j-wt[i]]+v[i]);这一现象,我们可以更改一下输入,即 

80 2

30 20

18 10

比如要求:dp[2][30]=max(dp[1][30],dp[2][30-18]+v[2])=max(20,10)=20,前2种瓜子,当资金为30,最大的重量为20.因为当资金30时,如果不选择买18元的瓜子,就有20千克,如果选择买18元的瓜子,最终的重量就只有10千克

注意:这里是一种瓜子可以被选择多次。

数据的输出

n代表瓜子的数量,w代表钱的数量,对于本道题而言就是dp[2][80]=50 

递推表格(演示样例,当资金=80元,瓜子种类数为2种时,最大的重量):

 

代码实现:

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

int dp[50][1010];  //表示,前i个物品,在价值w的情况下,最大的重量是dp[i][w]
int wt[1010],v[50]; //wt是价格,v是重量 
int n,w;	 
int main()
{
	cin>>w>>n; 		
	for(int i=1;i<=n;i++)
		cin>>wt[i]>>v[i];  //wt[i]代表第i种瓜子多少钱   v[i]代表第i种瓜子多少千克 
	for(int i=1;i<=n;i++)  //遍历n种瓜子,每种要么选,要么不选 
	{
		for(int j=1;j<=w;j++)	//j是价格 
		{
			if(j>=wt[i])  //wt是价格 
			{
				if(dp[i-1][j]>dp[i][j-wt[i]]+v[i])
				{
					dp[i][j]=dp[i-1][j];
				}
				else
				{
					dp[i][j]=dp[i][j-wt[i]]+v[i];
				}
	
			}
			else
            {
				dp[i][j]=dp[i-1][j];  
			}
		}
	}
	cout<<dp[n][w]<<endl;
 	return 0;
}

总结:

动态规划的题目还是要多做,做着做着自然会有灵感,如果你一开始觉得很难,就不去动手敲代码,那你永远也学不会动态规划,不仅动态规划,其它题目也是如此。最后送给大家一句话学如逆水行舟不进则退。

大家可以进微信群讨论数据结构与算法,也有资源分享给大家:微信号Q1313135,非诚勿扰.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值