POJ 3040 Allowance【贪心】

POJ 3040

题意:

给奶牛发工资,每周至少 C 元。约翰手头上有面值V_i的硬币B_i个,这些硬币的最小公约数为硬币的最小面值。求最多能发几周?

分析:

贪心策略是使多发的面额最小(最优解)。分三个阶段:

1. 首先面额不小于C的硬币属于没办法节约的类型,先统统发掉。

2. 然后对硬币面额从大到小尽量凑得接近C,允许等于或不足C,但是不能超出C。

3. 接着按硬币面额从小到大凑满C(凑满的意思是允许超出一个最小面值,ps此处的最小面值指的是硬币剩余量不为0的那些硬币中的最小面值),凑满之后得出了最优解,发掉,进入步骤2.

这样就保证了每次都是当前的最优解,这个题很好地体现了贪心法的精髓。

从大到小排序,只要不超额就能放多少就放多少,最后再从小的开始找一个放进去能超额的。

正确性证明:

因为大的是小的倍数,所以大的放进去不超额一定要放进去,因为小的不管怎么取,再超过c之前一定会凑成这个大的面额,那么用大的代替一定更优。第一步做完之后,那么现在一定要再放进去一个硬币,那么选择最小的并且能大于c的也一定是最优的。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn=22;
int need[maxn];
struct node
{
    int v,b;
    bool operator <(const node & x) const
    {
        return v>x.v;
    }
}coin[maxn];

int main()
{
    int n,c;
    while(scanf("%d %d",&n,&c)!=EOF)
    {
        for(int i=1;i<=n;i++)
            scanf("%d %d",&coin[i].v,&coin[i].b);
        sort(coin+1,coin+1+n);
        int ans=0;
        while(1)
        {
            memset(need,0,sizeof(need));
            int sum=c;
            for(int i=1;i<=n;i++)
            {
                int tmp = sum/coin[i].v;
                need[i]=min(tmp,coin[i].b);
                sum-=need[i]*coin[i].v;
            }
            if(sum>0)
            {
                for(int i=n;i>=1;i--)
                {
                    if(coin[i].b&&coin[i].v>=sum)// 从小到大找一个面值大于剩余sum的
                    {
                        need[i]++;
                        sum=0;
                        break;
                    }
                }
            }
            if(sum>0)break;
            int weeks=2e9;
            for(int i=1;i<=n;i++)
            {
                if(need[i])
                    weeks=min(weeks,coin[i].b/need[i]);
            }
            for(int i=1;i<=n;i++)
                if(need[i])
                    coin[i].b-=weeks*need[i];
            ans+=weeks;
        }
        cout<<ans<<endl;
    }
    return 0;
}
原文地址:https://www.cnblogs.com/demian/p/6560926.html