牛客练习赛86 C. 取钱(贪心/二分查找/DP/好题)
链接:https://ac.nowcoder.com/acm/contest/11176/C
来源:牛客网
题目描述
某个国家的货币体系里有 nn 种面值不同的纸币,从小到大面值分别是 a1,a2,...,ana1,a2,...,an,a1=1a1=1。
当取款人去 ATMATM 机取款 cc 元的时候, ATMATM 机按一种贪心的策略给取款人发放钞票,策略如下:
首先,已发放钞票的集合是空集,每一次 ATMATM 机都会放一张当前可以放的最大面值的钞票进去,一直到集合里纸币面额等于 cc,然后将集合里的纸币吐给取款人。
现在这个国家的公民觉得用钱把裤兜装的满满的会很有面子,但是他们又不想取出太多钱。现在他们问你:在最多取 bibi 元的情况下,最多可以获得多少张纸币。
输入描述:
第一行一个正整数 nn,表示钞票的面值数第二行 nn 个正整数 a1,a2,...,ana1,a2,...,an,表示 nn 种钞票的面值第三行一个正整数 qq,表示公民询问的数量接下来 qq 行每行一个整数 bibi1≤n≤2×1051≤n≤2×1051=a1<a2<...<an≤10181=a1<a2<...<an≤10181≤q≤2×1051≤q≤2×1051≤bi≤10181≤bi≤1018
输出描述:
对于每个查询输出两个数字:取款的金额以及获得的纸币数量若在不超过 bibi 的情况下有多个金额取款获得的纸币数量都是最大值,输出任意一个金额即可
示例1
输入
复制
4
1 5 10 50
3
2
8
50
输出
复制
2 2
8 4
49 9
比赛的时候没想到二分的是凑出的钱数==也没想到怎么求凑出的钱数,看了别人的码总算会了
关键思路就是贪心地用面额小的钱去凑。对于每个面值,先处理出总和小于下一个钞票面值的最多钞票数以及面值总和,然后对于每个输入w,二分查找最后一个面值总和x小于等于w的位置pos,算出w - x,这一部分就用a[pos + 1]来尽力凑,如果w - x < a[pos + 1]说明利用不上,那么直接设要取的钱是sum[pos]即可(题目说在最多取 bi元的情况下,当然也可以取不到)。
#include <bits/stdc++.h>
#define N 200005
using namespace std;
long long a[N], num[N], sum[N];//num[i]就是在凑出来的钱数小于a[i + 1]的情况下用面值小于等于a[i]的钞票能够得到的最多的张数 sum[i]为这些张数对应的钱数(小于a[i + 1])
int n, q;
int main() {
cin >> n;
for(int i = 1; i <= n; i++) {
cin >> a[i];
}
num[1] = sum[1] = a[2] - 1;//因为a1一定等于1
for(int i = 2; i <= n; i++) {
//贪心原则,优先用面值小的来凑,因此可以正着更新,有dp内味了
num[i] = num[i - 1] + (a[i + 1] - 1 - sum[i - 1]) / a[i];//要求钱数小于a[i + 1],所以这里是a[i + 1] - 1 - sum[i - 1]
sum[i] = sum[i - 1] + (a[i + 1] - 1 - sum[i - 1]) / a[i] * a[i];
}
cin >> q;
for(int i = 1; i <= q; i++) {
long long w;
cin >> w;
int pos = upper_bound(sum + 1, sum + n, w) - sum - 1;//找到最后一个小于等于w的位置,注意这里的上界,最多只能查找到n - 1的位置
cout << sum[pos] + (w - sum[pos]) / a[pos + 1] * a[pos + 1] << " " << num[pos] + (w - sum[pos]) / a[pos + 1] << endl;//注意剩下的钱必须要用a[pos + 1]来凑(题目要求)。注意这里的w不能-1,因为凑出来的钱数可以等于w
}
return 0;
}