HDU-2546-饭卡
Description
电子科大本部食堂的饭卡有一种很诡异的设计,即在购买之前判断余额。如果购买一个商品之前,卡上的剩余金额大于或等于5元,就一定可以购买成功(即使购买后卡上余额为负),否则无法购买(即使金额足够)。所以大家都希望尽量使卡上的余额最少。
某天,食堂中有n种菜出售,每种菜可购买一次。已知每种菜的价格以及卡上的余额,问最少可使卡上的余额为多少。
Input
多组数据。对于每组数据:
第一行为正整数n,表示菜的数量。n<=1000。
第二行包括n个正整数,表示每种菜的价格。价格不超过50。
第三行包括一个正整数m,表示卡上的余额。m<=1000。
n=0表示数据结束。
Output
对于每组输入,输出一行,包含一个整数,表示卡上可能的最小余额。
Sample Input
1
50
5
10
1 2 3 2 1 1 2 3 2 1
50
0
Sample Output
-45
32
题意
- 有多组数据,每组第一行输入一个N,代表有N种菜
- 第二行是N种菜各自的价格,第三行是卡上的余额
- 直到N为零程序结束
- 卡上剩余金额大于等于5元即可购买,即使购买后卡上余额为负数
- 卡上剩余金额小于5元不可购买,即使剩余金额足够买某些菜
- 每种菜只能被购买一次
- 对于每组数据求卡上最少的余额是多少
题解
- 这也是一个01背包的模板题
- 注意循环多组输入到N==0退出
- 不同的是,这题有金额5的分界,以及余额可为负数,输出剩余金额
- 那么不妨 调用knapsack_01()前先将v(卡上余额)减5
- 那么在knapsack_01()函数内部,卡上金额不可为负数,则和普通01背包一样,全部金额都可用于购买
- 还要注意的是!!!要使卡上余额最少
- 在尽可能花光减了5后的所有金额后,仍保留了至少5元的底数
- 可以凭着剩余的金额再买一份菜,这份菜越贵越好
- 所以调用knapsack_01()前可对所有价格进行sort排序
- 保留最大的不参与循环(若升序排序,则即为第n个)
- 最后要返回剩余金额的时候,v+5-cost[n]-dp[v]
- 即加回5,减去最贵的,减去想尽可能花光预留5元后其余金额的实际花费(不一定真的花光了)
涉及知识点
- 背包 算法(此题属于01背包)
- 对于背包的算法-详见链接博客介绍背包
AC代码
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
const int maxn=1e3+10;
int T,n,v,value[maxn],cost[maxn],dp[maxn];
int knapsack_01()
{
memset(dp,0,sizeof(dp));
for(int i=n-1;i>=1;--i)//for(int i=1;i<=n-1;++i),外层循环正序逆序两者均可
//排序值为找最大,且最大不参与遍历,其他可当做乱序一个个遍历即可
{//遍历到i,就表示遍历到第i个物品,优化二维数组变为一位数组
for(int j=v;j>=cost[i];--j)//从大到小遍历!!!
/*
以防同一个物品从小到大的话
先把小体积优化好,再优化大体积
那么大体积用的小体积dp可能是放这个物品优化过的
但01背包,每个物品只有一个,不可重复放
*/
{
if(cost[i]<=j) dp[j]=max(dp[j],dp[j-cost[i]]+cost[i]);
}//dp[v]即用到只剩5元能买多少钱的东西,从第二贵的开始,最贵的只剩5元的时候买
}
return v+5-cost[n]-dp[v];
}
int main()
{
while(scanf("%d",&n)&&n)
{
memset(cost,0,sizeof(cost));
for(int i=1;i<=n;++i) scanf("%d",&cost[i]);
scanf("%d",&v);
if(v<5)
{
printf("%d\n",v);
continue;
}
v-=5;
sort(cost+1,cost+n+1);
printf("%d\n",knapsack_01());
}
return 0;
}