poj-3040 Allowance (贪心)
http://poj.org/problem?id=3040
FJ 有n种不同面值的硬币,每种硬币都有相应的个数,大面值的硬币值总能被小面值的硬币值整除,每周需要支付 Bessie c元,问最多能支付Bessie多少周。
这题之所以能贪心,据说关键是这句话 where each denomination of coin evenly divides the next-larger denomination。
如果没有这个限制条件,有些情况是取不到最优解的。
把面值从小到大排序,然后从尾到头开始扫,能给多少就给多少,如果面值不够,就从头到尾扫,尽量取刚好超过数额的。
参考了别人代码,感觉写的很精妙。自己的水平还很欠缺。
1 #include <iostream> 2 #include <cstdio> 3 #include <cmath> 4 #include <vector> 5 #include <cstring> 6 #include <string> 7 #include <algorithm> 8 #include <string> 9 #include <set> 10 #include <functional> 11 #include <numeric> 12 #include <sstream> 13 #include <stack> 14 #include <map> 15 #include <queue> 16 17 #define CL(arr, val) memset(arr, val, sizeof(arr)) 18 19 #define ll long long 20 #define inf 0x7f7f7f7f 21 #define lc l,m,rt<<1 22 #define rc m + 1,r,rt<<1|1 23 #define pi acos(-1.0) 24 25 #define L(x) (x) << 1 26 #define R(x) (x) << 1 | 1 27 #define MID(l, r) (l + r) >> 1 28 #define Min(x, y) (x) < (y) ? (x) : (y) 29 #define Max(x, y) (x) < (y) ? (y) : (x) 30 #define E(x) (1 << (x)) 31 #define iabs(x) (x) < 0 ? -(x) : (x) 32 #define OUT(x) printf("%I64d\n", x) 33 #define lowbit(x) (x)&(-x) 34 #define Read() freopen("a.txt", "r", stdin) 35 #define Write() freopen("b.txt", "w", stdout); 36 #define maxn 1000000000 37 #define N 100010 38 using namespace std; 39 40 pair<int,int>p[25]; 41 int use[25]; 42 int main() 43 { 44 int n,c,sum; 45 scanf("%d%d",&n,&c); 46 sum=0; 47 for(int i=0;i<n;i++) scanf("%d%d",&p[i].first,&p[i].second); 48 sort(p,p+n); 49 for(int i=0;i<n;i++) //从大面值 开始 如果面值 大于c 那么直接累加。 50 if(p[i].first>=c) 51 { 52 sum+=p[i].second; 53 p[i].second=0; 54 } 55 while(true) //不断循环,直到不能在支付为止。 56 { 57 int tmp=c; 58 int flag=0; 59 memset(use,0,sizeof(use)); //记录每个面值的硬币使用了多少。 60 for(int i=n-1;i>=0;i--) //从大面值开始扫。 61 { //注意这里只有一次循环,就把所有面值的使用情况扫出来了,因为如果不能用当前面值支付的话 k会变成0,并且要么会刚好支付完,要么tmp>0 62 if(p[i].second) //当前面值还有 不会出现浪费当前面值的情况 63 { 64 int k=tmp/p[i].first; //为了支付c元 当前面值最多可以用多少个, 65 int mi=min(k,p[i].second); //取最小值 66 tmp-=p[i].first*mi; //剩下 这么多 67 use[i]=mi; //记录使用数量 68 if(tmp<=0) {flag=1;break;} //tmp小于0 那么本次支付已结束 69 } 70 } 71 if(tmp>0) //否则从小到大开始 给 72 { 73 for(int i=0;i<n;i++) 74 { 75 if(p[i].second>use[i]) //如果没使用完 76 { 77 while(use[i]<p[i].second) //一直使用,因为当前用的面值是最小的那么这样也是最优的。 78 { 79 tmp-=p[i].first; 80 use[i]++; 81 if(tmp<=0) {flag=1;break;} 82 } 83 if(tmp<=0&&flag==1) break; //只有 flag==1的时候 tmp<=0 84 } 85 } 86 } 87 if(!flag) break; //已经不能完成支付 88 int mx=maxn; 89 for(int i=n-1;i>=0;i--) if(use[i]) mx=min(p[i].second/use[i],mx); //use[i]是一次支付时当前面值使用的情况,那么p[i].second/use[i] 就表示可以支付 这么多次,并且取的是所有当中的最小值。 90 sum+=mx; 91 for(int i=n-1;i>=0;i--) if(use[i]) p[i].second-=mx*use[i]; //取用了 mx次,所以相应数量减少这么多,这样会节省很多时间,不用每次重复计算,是一次很大的优化 92 } 93 printf("%d\n",sum); 94 return 0; 95 }