熟能生巧,大多数的解题步骤或者思路都可通过训练获得。将事情重复做,即使刚开始你感觉素手无错,一脸茫然,但是通过重复训练,最终也能够游刃有余。
题目来源:第十二届蓝桥杯青少年国赛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,非诚勿扰.