题目描述

As a reward for record milk production, Farmer John has decided to start paying Bessie a small weekly allowance.
FJ has a set of coins in different denominations, where each denomination of coin evenly divides the next-larger denomination.
Using the given set of coins, he would like to pay Bessie at least some given amount of money every week. Please help him compute the maximum number of weeks he can pay Bessie.
POINTS: 250

输入描述:

* Line 1: Two space-separated integers: N and C
* Lines 2..N+1: Each line corresponds to a denomination of coin and contains two integers: the value V (1 <= V <= 100,000,000) of the denomination, and the number of coins B (1 <= B <= 1,000,000) of this denomation in Farmer John's possession.

输出描述:

* Line 1: A single integer that is the number of weeks Farmer John can pay Bessie at least C allowance

示例1

输入
3 6
10 1
1 100
5 120
输出
111
说明
FJ would like to pay Bessie 6 cents per week. He has 100 1-cent coins, 120 5-cent coins, and 1 10-cent coin.
FJ can overpay Bessie with the one 10-cent coin for 1 week, then pay Bessie two 5-cent coins for 10 weeks and then pay Bessie one 1-cent coin and one 5-cent coin for 100 weeks.

解答

题目大意

有N种硬币(),给出每种硬币的币值和数量,所有硬币的币值的最大公约数等于最小币值,求最多能凑成多少份>=C的钱?

基本思路

基本的贪心思路是这样来的:比如凑钱的目标是,一份钱C= 6,而我们拥有的最大币值硬币为5块钱,数量有10个,那么我们先拿5去凑,还差1;接着我们就看看剩余的币种中能凑出多少份1,如果能凑出10份(含以上),那么10枚5块钱硬币就能和这10份1块钱一起凑成10份6块钱;如果不能凑出10份1块钱,比如说只能凑出8份,那么就凑成8份6块钱,这时还剩两枚5块钱硬币,就拿这两枚硬币一起凑成一份(虽然拿10块去凑6块有点浪费,但如果不这么凑,这余下的两枚5块钱就没用了)。
就这样,我们算出来了在使用5块钱来凑的情况下,最多能凑出来多少份。接着用同样的思路讨论币值小的硬币即可。

递归函数设计

注意TIPs:
- a[i]表示第i种硬币, 其中a[i].v表示第i种硬币的币值,a[i].c表示第i种硬币的(剩余)数量
- 除法在不能整除的情况下均向下取整,除非加上ceil()表示向上取整.
递归函数 apply(i, mo, maxc)表示, 从第i种硬币开始(已降序排序)取钱,能取出多少份不少于mo的钱,且份数不超过maxc。也即,这个函数的返回值不超过maxc。
比如,上面例子的初始情况对应的函数调用是apply(0, 6, INF),第0种硬币的币值为5, 即a[0].v = 5, a[0].c = 10,接着递归调用apply(1, 1, 10)看看能凑成多少份1块钱。
设计出函数后,下面来讨论递归中如何转移。
 如果a[i].v <= mo(对应代码中的para > 0)
那么我们就先拿a[i]来凑,直到差不多凑满,再由剩下的币种来凑够mo % a[i].v。需要多少份(mo % a[i].v)的钱呢?仔细算一下。
先考虑para = mo/a[i].v, 即凑一份mo需要多少枚硬币a[i],这里把para枚硬币描述为一堆吧,即这一堆币值为a[i]的硬币再加上一点别的硬币就能凑成一份mo的钱。
第i种硬币能分成多少堆呢,应该是cnt = a[i].c / para堆;即这cnt堆钱,每堆的钱数是a[i].v*para, 还需要再加上(mo - a[i].v*para), 即(mo % a[i].v) 这么多的钱就能凑出一份mo.
那么我们还需要min(cnt, maxc)份价值为(mo % a[i].v) 的钱,即递归调用apply(i+1,mo-a[i].v,min(cnt,maxc));
递归调用的返回值将告诉我们,这min(cnt, maxc)堆钱能不能全部凑满,如果不能,即会剩余一些a[i]的硬币。另外,本身我们分堆的时候,就会留下a[i].c % para枚硬币,这些剩余的硬币需要继续讨论。
如果剩余硬币数量a[i].c * a[i].v >= mo,
那么就直接拿这些硬币凑钱,即拿同一币值(a[i].v)数量为a[i].c的硬币去凑mo, 显然能凑a[i].c/(ceil(mo/a[i].v))份。
如果依然剩余硬币,a[i].c * a[i].v < mo,
那么拿这些硬币凑成一堆a[i].c * a[i].v的钱,还差mo - a[i].c * a[i].v 才能凑成一份,即递归调用apply(i+1, mo - a[i].c * a[i].v, 1)。
讨论到这里,如果第i种硬币还有剩余,这些硬币可以直接忽略掉了,因为,如果还有剩余硬币剩余,说明加上后面所有的硬币也凑不够mo,也就是说,这时候整个程序都能结束了。如果第i种硬币没有剩余,那么后面硬币的钱才有可能有足够多的钱来凑,我们应该继续算后面的硬币能凑成多少份mo,即递归调用apply(i+1,mo,maxc)。
 如果a[i].v > mo(对应代码中的para == 0)
这时候一块硬币a[i]就能凑成mo的钱数,那么也就是能凑成a[i].v份。 但是,考虑到,a[i].v比mo要大,那么直接拿a[i]来凑,会造成浪费。其实,我们应该先拿后面的硬币来凑(递归调用apply(i+1,mo,maxc)),如果还凑不满maxc份mo,才拿a[i] 来凑。

source code

#include <cstdio>
#include <algorithm>
using namespace std;
#define UPDATE res += _update;\
               if (res >= maxc) \
               {                \
                return res;\
               }
// UPDATE 宏来保证凑的钱的份数不超过maxc

typedef long long ll;
int n;
ll c;
const int maxn = 25;
const ll INF = 100000000000003LL;
struct money
{
    ll v,c;
}a[maxn];
bool cmp(const money &a, const money &b)
{
    return a.v > b.v;
}
ll ans = 0;
ll apply(int i, ll mo, ll maxc) // return how many moneys(mo) can produce
{
    ll _update = 0, res = 0;
    if (mo <= 0) return maxc;//已经凑够钱,不需要再加硬币
    if (i >= n) return 0; //已经没有新的硬币可以用了
    // 两个条件的判断顺序不能弄混

    ll para = mo / a[i].v; // amount of a[i] per heep
    if (para > 0) // 也即 (mo >= a[i].v)
    {
        ll cnt = a[i].c / para;// cnt: how many heeps can a[i] be devided into 
        _update = apply(i + 1, mo%a[i].v, min(cnt, maxc - res));
        a[i].c -= _update * para;
        UPDATE

        if (a[i].c && mo < a[i].v * a[i].c)
        {
            ll _para = para + (mo%a[i].v?1:0);
            _update = min(a[i].c / _para, maxc - res);
            a[i].c -= _update * _para;
            UPDATE
        }

        if (a[i].c && mo >= a[i].v * a[i].c)
        {
            _update = apply(i + 1, mo - a[i].v * a[i].c, 1);
            a[i].c -= _update  * a[i].c;
            UPDATE
        }
        if (a[i].c) return res;
        _update = apply(i + 1, mo, maxc - res);
        UPDATE
    }

    else// para == 0
    {
        _update = apply(i + 1, mo, maxc);
        UPDATE

        _update = min(a[i].c, maxc - res);
        a[i].c -= _update;
        UPDATE
    }
    return res;
}

int main()
{
    scanf("%d%lld",&n,&c);
    for (int i = 0; i < n; i++)
        scanf("%lld%lld",&a[i].v,&a[i].c);
    sort(a,a+n,cmp);
    printf("%lld\n",apply(0,c,INF));
}


来源:DaF_alex