Description
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.
题目大意:
有n中硬币,每种硬币有对应的数量,且保证大面额一定是小面额的倍数,现在每周要付给工人至少c元,问最多能支付多少周
解题思路:
考虑一次支付的过程,大于c的硬币每个只能用于支付一周,关键在于面值小于c的硬币该如何分配。
首先我们应该要尽量选择面额大的,这样在数额接近c的时候,我们能有更多的小面额去组成更多的组合,从而使数额在满足条件的时候尽可能地接近c,即每次浪费最少的钱。
题目中还有关键的一点是保证大面额一定是小面额的倍数,这保证了小面额在组成比大面额大的数额时,一定会先组成大面额的数额,那么不妨用大面额去替换,原因刚才已经解释了。
解题步骤:
先用大面额的去组成c,但不要超过c,然后再用小面额去填补差额,如果小面额无法填补差额,则再使用一个大面额硬币中的小面额硬币去填补。注意上述过程不能直接模拟,否则会t,应该在求得一种支付方案后就尽可能地使用该方案,直到某一种硬币不能满足方案要求,然后就在新的硬币配置情况下求解新的方案。
具体实现见代码注释
AC代码
#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<math.h>
using namespace std;
#define INF 0x3fffffff
struct node {
long long v, num;
int t;//t代表方案中该硬币的使用数量
}list[21];
long long c, ans;
bool cmp(node a, node b) {//根据面额从大到小排序
return a.v > b.v;
}
int main() {
int n;
while (cin >> n >> c) {
ans = 0;
for (int i = 1; i <= n; i++) {
scanf("%lld%lld", &list[i].v, &list[i].num);
//大于c的硬币每个只能用于支付一周
if (list[i].v >= c) { ans += list[i].num; i--; n--; }
}//list中value都是小于c的
sort(list + 1, list + 1 + n, cmp);
while (n > 0) {
long long tmp = c;
for (int i = 1; i <= n; i++) {//求出支付方案
list[i].t = 0;
while (tmp >= list[i].v) {
if (list[i].t < list[i].num) {
list[i].t++; tmp -= list[i].v;
}
else {
break;
}
}
if (i == n) {//已经考虑完所有的面额
if (tmp > 0) {//如果差额还没被消除 即小面额无法填补差额
int j;
for (j = n; j > 0; j--) {//找到还有富余的最小面额
if (list[j].t < list[j].num) {
list[j].t++; tmp -= list[j].v;
break;
}
}
for (int k = n; k > j; k--) {//本方案中其余更小的面额就不再使用了
list[k].t = 0;
}
}
}
}
if (tmp > 0) { break; }//如果无力支付c,结束
long long ans_tmp = INF;
for (int i = 1; i <= n; i++) {
if (list[i].t == 0) { continue; }
ans_tmp = min(ans_tmp, list[i].num / list[i].t);//方案能够使用的最大次数
}
ans += ans_tmp;
for (int i = 1; i <= n; i++) {
list[i].num -= (list[i].t*ans_tmp);//各类硬币的剩余数量
}
while (n > 0 && list[n].num == 0) { n--; }//硬币数量为0则削减硬币种数
for (int i = 1; i <= n; i++) {
if (list[i].num == 0) {//硬币数量为0则削减硬币种数
//将最后一个数量不为0的硬币提到该位置
list[i].v = list[n].v; list[i].num = list[n].num; n--;
while (n > 0 && list[n].num == 0) { n--; }//硬币数量为0则削减硬币种数
}
}
sort(list + 1, list + 1 + n, cmp);//重新排序
}
cout << ans << endl;
}
}
————————————————————————————————————
刚刚参考了一下大神们的博客,发现在求得支付方案时,可以先不管硬币的剩余数量,使用k=tmp/list[i].v; m=min(k,list[i].num); tmp-=m*list[i].v; list[i].t=m;
这样做的话就不用在硬币数量为0时削减硬币种数了,也不用将最后一个数量不为0的硬币向前提了,不容易出错哦( ̄︶ ̄)
然后我就发现,我的代码中也已经有了类似的判断list[i].t < list[i].num
,所以本来就不用额外地削减硬币种类什么的。。。。。 汗-_-||
精简后的AC代码
#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<math.h>
using namespace std;
#define INF 0x3fffffff
struct node {
long long v, num;
int t;//t代表方案中该硬币的使用数量
}list[21];
long long c, ans;
bool cmp(node a, node b) {//根据面额从大到小排序
return a.v > b.v;
}
int main() {
int n;
while (cin >> n >> c) {
ans = 0;
for (int i = 1; i <= n; i++) {
scanf_s("%lld%lld", &list[i].v, &list[i].num);
//大于c的硬币每个只能用于支付一周
if (list[i].v >= c) { ans += list[i].num; i--; n--; }
}//list中value都是小于c的
sort(list + 1, list + 1 + n, cmp);
while (true) {
long long tmp = c;
for (int i = 1; i <= n; i++) {//求出支付方案
list[i].t = 0;
while (tmp >= list[i].v) {
if (list[i].t < list[i].num) {
list[i].t++; tmp -= list[i].v;
}
else {
break;
}
}
if (i == n) {//已经考虑完所有的面额
if (tmp > 0) {//如果差额还没被消除 即小面额无法填补差额
int j;
for (j = n; j > 0; j--) {//找到还有富余的最小面额
if (list[j].t < list[j].num) {
list[j].t++; tmp -= list[j].v;
break;
}
}
for (int k = n; k > j; k--) {//其余更小的面额就不再使用了
list[k].t = 0;
}
}
}
}
if (tmp > 0) { break; }//如果无力支付c,结束
long long ans_tmp = INF;
for (int i = 1; i <= n; i++) {
if (list[i].t == 0) { continue; }
ans_tmp = min(ans_tmp, list[i].num / list[i].t);//方案能够使用的最大次数
}
ans += ans_tmp;
for (int i = 1; i <= n; i++) {
list[i].num -= (list[i].t*ans_tmp);//各类硬币的剩余数量
}
}
cout << ans << endl;
}
}