挑战程序设计竞赛2.2习题:Allowance POJ - 3040

Posted jacobfun

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了挑战程序设计竞赛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.

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.
 
这道题目着实难看出怎么下手,我们都知道可怜的约翰家与世隔绝,没有银行,如果某张的数额比想要给多就只能都给小贝西(不找零),那么如果面额小于想给的钱怎么办呢?题目中有一句破题的关键句子: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
",ans);
    return 0;
}

 

以上是关于挑战程序设计竞赛2.2习题:Allowance POJ - 3040的主要内容,如果未能解决你的问题,请参考以下文章

挑战程序设计竞赛3.1习题:Moo University - Financial Aid POJ - 2010

挑战程序设计竞赛2.3习题:Cheapest Palindrome POJ - 3280

挑战程序设计竞赛2.3习题:Cow Exhibition POJ - 2184

挑战程序设计竞赛3.2习题:Bound Found POJ - 2566

[转] AOJ 0525 Osenbei《挑战程序设计竞赛(第2版)》练习题答案

挑战程序设计竞赛2.3习题:Making the Grade POJ - 3666