tag : 贪心
WA N次,对拍N次,修改N次后,终于完成这道题。
理解题意后,很容易想到用贪心,从币值大的硬币开始取,然后递归求解。
但这个递归,写起来不怎么容易。可能是因为我没有事先在纸上把递归的思路,要讨论的情况写清楚,而是边写边想会遇到什么情况,以致于讨论起来非常乱,代码难写的同时,漏讨论了一种情况,外加细节写错导致多次WA。
下面先总结下自己的思路。
有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] 来凑。
#include
#include
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));
}