POJ 3040 Allowance 题解

来源:互联网 发布:电子白板软件下载 编辑:程序博客网 时间:2024/06/16 02:10

tag : 贪心

WA N次,对拍N次,修改N次后,终于完成这道题。

理解题意后,很容易想到用贪心,从币值大的硬币开始取,然后递归求解。

但这个递归,写起来不怎么容易。可能是因为我没有事先在纸上把递归的思路,要讨论的情况写清楚,而是边写边想会遇到什么情况,以致于讨论起来非常乱,代码难写的同时,漏讨论了一种情况,外加细节写错导致多次WA。

下面先总结下自己的思路。

    • 题目大意
    • 基本思路
    • 递归函数设计
    • source code


题目大意

有N种硬币(N <= 20),给出每种硬币的币值和数量,所有硬币的币值的最大公约数等于最小币值,求最多能凑成多少份>=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块钱。

设计出函数后,下面来讨论递归中如何转移。


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)。


2. 如果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 宏来保证凑的钱的份数不超过maxctypedef 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));}
原创粉丝点击