题意:
给奶牛发工资,每周至少 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; }