题目描述:
Too Rich
Time Limit: 6000/3000 MS (Java/Others) Memory Limit: 262144/262144 K (Java/Others)
Total Submission(s): 169 Accepted Submission(s): 50
Problem Description
You are a rich person, and you think your wallet is too heavy and full now. So you want to give me some money by buying a lovely pusheen sticker which costs p dollars from me. To make your wallet lighter, you decide to pay exactly p dollars by as many coins and/or banknotes as possible.
For example, if p=17 and you have two 10coins,four 5 coins, and eight 1coins,youwillpayitbytwo 5 coins and seven $1 coins. But this task is incredibly hard since you are too rich and the sticker is too expensive and pusheen is too lovely, please write a program to calculate the best solution.
Input
The first line contains an integer T indicating the total number of test cases. Each test case is a line with 11 integers p,c1,c5,c10,c20,c50,c100,c200,c500,c1000,c2000, specifying the price of the pusheen sticker, and the number of coins and banknotes in each denomination. The number ci means how many coins/banknotes in denominations of i dollars in your wallet.
1≤T≤20000
0≤p≤109
0≤ci≤100000
Output
For each test case, please output the maximum number of coins and/or banknotes he can pay for exactly p dollars in a line. If you cannot pay for exactly p dollars, please simply output ‘-1’.
Sample Input
3
17 8 4 2 0 0 0 0 0 0 0
100 99 0 0 0 0 0 0 0 0 0
2015 9 8 7 6 5 4 3 2 1 0
Sample Output
9
-1
36
题解:
很好的一道性质贪心题,比赛时想了一半没想全真是笨…
首先发现钱的规律,好像前面的都是后面的约数,因为我们要凑p的钱,那么感觉上从后往前贪心,能够用就狂用,因为后面的不管怎么凑都是要达到一下这个数的.
但是小心,上面的那个贪心是不对的.对于1 5 10 20 目前来说对,但是1 5 10 20 50 100,的话, 100那个点就不对了,注意到20和50虽然都是100的约数,但是20不是50的约数,前面的不一定能够凑出100,比如20*3+50.于是发现不行,但是无所谓.因为我们发现只要后一个是前一个的倍数就可以了,而给的钱特殊的只有20和50 以及 200和500这两组. 我们这样处理:
枚举50用了奇数还是偶数个,因为偶数个50可以当做权值为2的100来用,满足我们的条件,而奇数的话,我们在计算之前就把这个50减掉就好. 200和500同样. ——————这是这一道题很重要的一个性质.
还有一个条件,就是用尽量多的硬币,我们上面搞是用尽量少的硬币,怎么办?其实就是反向思维.我们凑剩下的钱数,那么就是用尽量少的啦:)
另外,金爷教的正向做的方法.
正向做,就是尽量多的使用硬币.从一块开始用,能用尽量用,然后发现之后能继续凑的都是5的倍数了,那么我们要求尽量用1的结果后再使剩下的数是5的倍数,不行的话就减少1的个数. 一直这样搞.
特殊的有两个地方:
(1)20和50之前的那个10不知道要凑20或50的倍数,仍然利用暴力枚举50奇偶的方法来搞.
(2)有可能比如说没办法退5来使得是10的倍数,因为5本来就用的只是0个,那么其实应该退5个1来搞.
重点:
(1)逆向凑的话,全是前一个是后一个的约数的才能贪心.利用奇偶的暴力来搞
(2)逆向思维搞出最少.
(3)正向的话,一直用最多硬币这个贪心,并在满足可以凑成p的前提下没办法的调整.
代码:
//代码是逆向做的
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
using namespace std;
typedef long long ll;
const int maxn = 20;
const int INF = 1e9;
const int N = 10;
int vis[maxn];
int val[maxn], have[maxn], f[maxn], p,allnum, allval;
int ans;
int gao(int p, int f[], int ans)
{
for(int i = N-1;i>=0;i--)
{
int t = f[i];
t = min(t, p/val[i]);
if(i==4 || i==7)
{
if(t%2==1)
t--;
}
p -= t*val[i];
ans += t;
}
if(p!=0)
return -1;
return ans;
}
void solve()
{
int result = -1;
allnum = 0;
allval = 0;
for(int i = 0; i<N; i++)
{
allnum += have[i];
allval += have[i]*val[i];
}
p = allval - p;
if(p<0)
{
printf("-1\n");
return;
}
for(int sta = 0;sta<(1<<2);sta++)
{
int pp = p, tmp=0;
for(int i = 0;i<N;i++)
f[i] = have[i];
if(((1<<0)&sta)&&have[4]>=1&&pp>=50)
{
pp -= 50;
f[4]--;
tmp++;
}
if(((1<<1)&sta)&&have[7]>=1&&pp>=500)
{
pp -= 500;
f[7]--;
tmp++;
}
int t = gao(pp, f, tmp);
if(t!=-1)
result = max(result, allnum - t);
}
printf("%d\n", result);
}
int main()
{
// freopen("Ain.txt", "r" ,stdin);
val[0] = 1;
val[1] = 5;
val[2] = 10;
val[3] = 20;
val[4] = 50;
val[5] = 100;
val[6] = 200;
val[7] = 500;
val[8] = 1000;
val[9] = 2000;
int ncase;
scanf("%d", &ncase);
while(ncase--)
{
scanf("%d", &p);
for(int i = 0; i<N; i++)
scanf("%d", &have[i]);
solve();
}
return 0;
}