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是pair對應的最大報銷額,如此一來可以節省不少時間。

同時在參考了網上解答的基礎上,我發現了原題中有一個條件沒有交代清楚,就是上述問題描述中的限制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();
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章