饭卡问题(0-1背包的变形)

电子科大本部食堂的饭卡有一种很诡异的设计,即在购买之前判断余额。如果购买一个商品之前,卡上的剩余金额大于或等于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

题目大意 :给你一些钱,让你去食堂消费,每个菜只能点一次,且5元以上可以任意点,5元以下不可以点,让你求最后剩下的最少钱。

题目分析 :我们只要把5元单独出来,用这5元消费最大的那个值。剩下的0-1背包就可以了。

题目收获 :动态转移方程的变形

AC代码 :
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#define maxn 1005
using namespace std;
int d[maxn];
bool cmp(int x, int y)
{
    return x > y;
}

int main()
{
    //freopen("in.txt", "r", stdin);
    int n,m;
    while (scanf("%d",&n)!=EOF && n)
    {
        int ni[maxn];//储存价格的数组
        memset(d, 0, sizeof(d));//清空上一次计算的值
        for (int i = 1; i <= n; i++)
            scanf("%d",&ni[i]);
        scanf("%d", &m);
        if (m < 5)//小于5元最直接输出
        {
            printf("%d
", m);
            continue;
        }
        sort(ni + 1, ni + 1 + n,cmp);//求最大值
        int ans = ni[1];//储存最大值
        ni[1] = 0;//最大者变为0
        for (int i = 1; i <= n; i++)//0-1模板
        {
            for (int j = m-5; j >= 1; j--)
            {
                if (j >= ni[i])
                    d[j] = max(d[j], d[j - ni[i]] + ni[i]);//转移方程有点点变化
            }
        }
        printf("%d
", (m - 5) - d[m - 5] + 5 - ans);
    }
    return 0;
}
原文地址:https://www.cnblogs.com/7750-13/p/7363949.html