今天讲的是动态规划,学长们拉了13道题让我们做一下,一下午大概4小时,做了5道题(还是太弱了 摊手)
题意:
电子科大本部食堂的饭卡有一种很诡异的设计,即在购买之前判断余额。如果购买一个商品之前,卡上的剩余金额大于或等于5元,就一定可以购买成功(即使购买后卡上余额为负),否则无法购买(即使金额足够)。所以大家都希望尽量使卡上的余额最少。
某天,食堂中有n种菜出售,每种菜可购买一次。已知每种菜的价格以及卡上的余额,问最少可使卡上的余额为多少。
题目分析:从每种菜只卖一次可知这道题是01背包
何为01背包?
01背包就是在给定背包容积和物品的质量和价值后,告知每个物品只能选一次,问背包可以装的最多的价值是多少的一类问题
然后发现这道题跟01背包的定义并不完全相同,因为它并没有给定你背包容量,其实仔细分析题目可以知道要想使这个余额最小,那么做法是先让余额接近5,然后用物品中质量最大的那一个去减,那么得到的余额就是最小余额;所以这道题就分成两部分,第一部分找价钱最高的那一个,把他标记一下,然后在遍历菜的时候,如果遍历到这个点就continue,然后就是对剩下的菜进行01背包处理,容积就是m-5,那么这道题就得到了解决
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
int dp[1010];//dp[I]表示第I个菜时,要扣的钱
int w[1010];
void ini()
{
memset(dp,0,sizeof(dp));//初始化很重要,不同的问题初始化的值不一样,因为本题是求最大的填满背包,所以是全部为0,如果是求最小值那么就要便利为0x3f;
}
int main()
{
int n;
while(scanf("%d",&n))
{
ini();
if(n==0) break;
int maxm=0;
int p;
for(int i=0;i<n;i++)
{
scanf("%d",&w[i]);
if(maxm<w[i])
{
maxm=w[i];
p=i;
}
}
int m;
scanf("%d",&m);
if(m<5)//这是一个特殊情况,不要忘记考虑;
{
printf("%d\n",m);
continue;
}
for(int i=0;i<n;i++)//模板开始
{
if(i==p) continue;//最大的值要留到最后时减去
for(int j=m-5;j>=w[i];j--)
{
dp[j]=max(dp[j],dp[j-w[i]]+w[i]);//01背包模板,这里是使用一个滚动的一维数组来储存,如果看不懂建议找张纸对着公式模拟一下
}
}//模板结束
printf("%d\n",m-dp[m-5]-w[p]);
}
return 0;
}
那么在解决这道题之后,我更想去思考一下,应该怎样从实际问题来抽象出dp问题,下面说一些我自己的理解,也便于以后的反思,如有不对,评论指正呀。
首先,对于dp的问题大体上可以分析出一些简单的细节,比如只能用一次,无限次使用,每个物品有几个,这些字都是01背包,完全背包,多重背包的关键字,看见他们就往这些方向思考或许会是个不错的选择,然后就是尝试分解题目,比如这道题,在思考后就明白,要想最小,那么就只有在余额最接近5时,再取用最大的去减,这样余额才最小,于是问题就分解为找最大值和求怎么样使余额在不减去最大值的情况下最接近5,这个问题不就是01背包的转换了吗?