挑战程序设计竞赛2.2习题:Allowance POJ - 3040
Allowance
As a reward for record milk production, Farmer John has decided to start paying Bessie the cow a small weekly allowance. FJ has a set of coins in N (1 <= N <= 20) different denominations, where each denomination of coin evenly divides the next-larger denomination (e.g., 1 cent coins, 5 cent coins, 10 cent coins, and 50 cent coins).Using the given set of coins, he would like to pay Bessie at least some given amount of money C (1 <= C <= 100,000,000) every week.Please help him ompute the maximum number of weeks he can pay Bessie.
Input
* 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.
* 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.
Output
* Line 1: A single integer that is the number of weeks Farmer John can pay Bessie at least C allowance
Sample Input
3 6 10 1 1 100 5 120
Sample Output
111
Hint
INPUT DETAILS:
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.
OUTPUT DETAILS:
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.
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.
OUTPUT DETAILS:
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.
这道题目着实难看出怎么下手,我们都知道可怜的约翰家与世隔绝,没有银行,如果某张的数额比想要给多就只能都给小贝西(不找零),那么如果面额小于想给的钱怎么办呢?题目中有一句破题的关键句子:each denomination of coin evenly divides the next-larger denomination.啥意思?就是某个面额是能被第一个比它大的面额整除的,我们先不考虑最小面额为一的情况,可以把想要兑换的钱想象成有n个最小面额构成的,当然可能存在兑换的钱不是最小面额的整数倍的情况。
如果是整数倍,那么我们把面额从大到小遍历着取,遍历每次都取得 (要给的钱是最小面额的整数倍数) / (该面额是最小面额的整数倍数) 张第i种的钱币(参考白书2.2硬币问题例题),这样可以使得值恰好满足或者略小于C。之所以从大到小,这样可以防止提前用完小面额不浪费。如果出现小于C,那么说明虽然为整数倍,但是题中给的条件没有办法刚好达到C(因为如果存在该情况,则在计算的时候就能取到了),于是乎从最小的开始寻找最小满足条件且还能取的面额,虽然会超过C,但是能保证超过的情况最少,然后寻找这样的取法最多能取多少次,如果把所有的取完,也无法使得面额超过C则找完了。(如下图)
但是,大部分情况可能没有办法被整数倍的最小面额整除,那么必定会产生剩余,其实也是一样的,先取使得小于等于C,由于此类情况必定无法满足恰好(因为不能整除)所以肯定要选一张最小且能使得值大于C的面额,取完的结束条件同上。(见下图)
为什么要把它分成无数个最小非一的面额格子呢?我们可以假设每次取一张纸币时就是取了一份格子数,如果能恰好取到C,则从大到小从小到大均可以做到,但是从小到大的话后面每次取到的格子份数大,可能会出现浪费,所以从大到小取不会浪费,参考白书贪心例题硬币问题。
如果最小面额是一,那么在找到第一次可行的方法时,如果不用一无法达到C的话,此时可以用面额为一的来补。如果不够也能够使得最后找到的结果大于C的最小,所以如果存在面额为一,我们也可以假定面额最小是第一个比一大的,然后从小到大补齐的时候用一补齐缺口,如果补不齐也可以再寻找下一个比一大的使得值大于C来满足条件。
总结:先把面额排序,面额大于C的每次用一张能用完,小于C的从大到小遍历寻找解决方案,遍历时保证每种面额取值最大且均使得加入后值小于等于C,此时若小于C则不可能刚好到达C,从小到大选取一张最小且满足条件的面额,由此得到方案,计算这样能操作多少次,然后再重复本方案直到所有面额用完也无法大于等于C结束。
AC代码:
#include <stdio.h> #include <algorithm> #include <string.h> using namespace std; const int INF = 0x3fffffff; struct Node{ int v; int p; friend bool operator <(Node x, Node y) { return x.v < y.v; } }money[25]; int used[25];//本次方法每种钱使用的张数 int ans; int start = -1;//如果全大于c则不需要管小于c的,因为没有 int main(void) { int n,c; scanf("%d%d", &n, &c); for(int i = 0; i < n; i++) { scanf("%d %d", &money[i].v, &money[i].p); } sort(money, money + n); for(int i = n - 1; i >= 0; i--) { if(money[i].v >= c) { ans += money[i].p; money[i].p = 0; } else { start = i;//面额小于C的纸币的最大序号,在start之后就都是比C大的纸币 break; } } while(1) { memset(used, 0, sizeof(used));//每种方法找之前每张钱币都没用过 int goal = c;//要找到值至少为C for(int i = start; i >= 0; i--) { if(money[i].p == 0) continue; int t = goal / money[i].v;//相当于每次该份的最大取走份数且能使得剩余的goal >= 0 used[i] = min(money[i].p, t); goal -= used[i] * money[i].v; if(goal == 0)//能刚好取完,就完成了一种方法 break; } if(goal > 0)//没办法使用现有条件刚刚好达到C { for(int i = 0; i <= start; i++)//把从小到大全加上直到刚好大于C为止 { if(money[i].p > used[i]) { int temp = min(money[i].p - used[i], (goal + money[i].v - 1) / money[i].v); // (goal + money[i].v - 1) / money[i].v就是假设第i种数量无限,要取几张能使得值恰好大于C goal -= money[i].v * temp; used[i] += temp; } } } if(goal > 0)//所有钱币都用完了还是没办法大于等于C break; int maxcopy = INF;//判断这样的操作能搞几次 for(int i = 0; i <= start ; i++) if(used[i]) maxcopy = min(maxcopy, money[i].p / used[i]); for(int i = 0; i <= start ; i++) money[i].p -= maxcopy * used[i]; ans += maxcopy; } printf("%d\n",ans); return 0; }