HDU -- 最大报销额(ACM Step: 3.3.8)

一,概述

1. 问题描述

给定最大可报销额度以及一定数量的发票,要求在发票中找出在最大可报销额度内的最大的发票报销额,有限制如下:

1)每张发票中可能有属于不同消费类别的项目,仅包括某些类别的发票才可报销

2)单张发票总金额不超过1000才可报销

3)单张发票中单个类别的项目金额不超过600才可报销

2. 问题链接

HDU -- 最大报销额(ACM Step: 3.3.8)

3. 问题截图

1.1 问题截图

二,算法思路

这道题特殊在给定的最大可报销额是未知的,并且不是整数,否则就是通常的01背包问题,所以用于解决01背包问题的做法不可行(即先根据给定的最大报销额构建数组,然后迭代的完成数组的求值)。但是考虑到发票的个数十分小(最多只有30),同时状态转移方程还是相同的(即还是F[i, v] = max(F[i-1, v], F[i-1, v-p[i]])),只是不能用数组去表示这样的方程(因为数组没有小数下标),所以这道题可以使用自上而下的方法去解答,即用递归的方法求解。

同时考虑到自上而下的求解可能会导致同一个子问题被多次求解,在实现上可以采用某种策略保存已经求解过的子问题结果,在本题中,我采用了map,完成了这个记录工作,map的key是pair<i, v>,表示前i张发票在最大可报销额是v时的情况,map的value是key对应的最大报销额,如此一来可以节省不少时间。

同时在参考了网上解答的基础上,我发现了原题中有一个条件没有交代清楚,就是上述问题描述中的限制3),题目中说“单项物品的价值不得超过600元“,这可能会引起混淆。

三,算法实现

#include <iostream>    // for cin, cout, endl
#include <cstdio>    // for printf
#include <map>    // for map
#include <utility>    // for pair, make_pair

using std::cin;
using std::cout;
using std::endl;
using std::map;
using std::pair;
using std::make_pair;

void input(int&);
double compute(double, int);
void output(double&);

const int MAX_CHECK = 30;    // the max num of check

map<pair<double, int>, double> cache;    // cache the calculated value for later reference
double check[MAX_CHECK+1];    // hold input, +1 for the index from 1~30

int main()
{
    double q;
    int n;
    double res;    // res for result

    while (cin>>q>>n && n!=0){
        input(n);
        res = compute(q, n);
        output(res);
    }
}

double max2(double a, double b)
{
    if (a > b)
        return a;
    else
        return b;
}

void input(int& n)
{
    double t, p;    // t for total, the total money of check; p for price, the price of each item of check
    char k, s;    // k for kind, the kind of check; s for skip, skip char ':'
    bool valid;    // indicate whether this check valid
    int num;    // the num of valid input
    double tmp[3];    // for hold the sum of A, B, C items
    int m;
    int i, j;

    num = 0;
    for (i=0; i<n; ++i){
    	for (j=0; j<3; ++j)
        	tmp[j] = 0.0;

        valid = true;
        t = 0.0;

        cin >> m;
        for (j=0; j<m; ++j){
            cin >> k >> s >> p;
            if (k>='A' && k<='C')
                tmp[k-'A'] += p;
            else
                valid = false;
            t += p;
        }
        // check if the price of each class or of total valid
        for (j=0; j<3; ++j)
            if (tmp[j] > 600.0)
                valid = false;
        if (t > 1000.0)
            valid = false;

        if (valid)
            check[++num] = t;
    }
    n = num;
}

double compute(double q, int n)
{
    // n==1, return check[n] if q>=check[n], return 0 if q<check[n]
    if (n <= 1){
        if (q >= check[n])
            return check[n];
        else
            return 0.0;
    }

    pair<double, int> put, nput;    // nput for not put, these indicate whether to put check n
    double put_v, nput_v;    // the corresponding value of above two states

    put_v = nput_v = double();

    nput = make_pair(q, n-1);
    nput_v = cache[nput];
    if (nput_v == double()){    // if the put state has not been calculated
        nput_v = compute(q, n-1);
        cache[nput] = nput_v;
    }
    if (q >= check[n]){    // now, the F[q, n] = max(F[q-check[n], n-1], F[q, n-1])
        put = make_pair(q-check[n], n-1);
        put_v = cache[put];
        if (put_v == double()){    // if the put state has not been calculated
            put_v = compute(q-check[n], n-1) + check[n];
            cache[put] = put_v;
        }
    }
    return max2(put_v, nput_v);
}

void output(double& res)
{
    printf("%.2f\n", res);
    // clear the map
    cache.clear();
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值