Allowance
Time Limit: 1000MS Memory Limit: 65536K
Total Submissions: 7489 Accepted: 2903
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 N N( 1 ≤ N ≤ 20 1≤N≤20 1≤N≤20) 种货币,第 i i i 种货币的价值是 V i V_i Vi,数量是 B i B_i Bi ,并保证当每种货币按价值从小到大排序后,后一种货币总是能被前一种整除。现在每周需要支付至少 C C C 的工资(多支付的相当于浪费掉),问至多能支付几周。
其中, 1 ≤ V i ≤ 1 0 8 , 1 ≤ B i ≤ 1 0 6 , 1 ≤ C ≤ 1 0 8 1≤V_i≤10^8, 1≤B_i≤10^6, 1≤C≤10^8 1≤Vi≤108,1≤Bi≤106,1≤C≤108
分析:
贪心策略:
先按价值排序。
在考虑当前状态下最优的支付一次 C C C 的方式时,只需正反两次遍历每种货币即可:
-
先贪心地按价值大到价值小的顺序考虑,首先设剩余未支付价值为 r e m C remC remC ,初始时赋值为 C C C . 在考虑到第 i i i 种货币时,设该种货币的价值为 V i V_i Vi , 当前的剩余数量为 B i B_i Bi , 此时还剩 r e m C remC remC 未被支付,则应使用 m i n ( B i , ⌊ r e m C V i ⌋ ) min(B_i,\lfloor \frac{remC}{V_i} \rfloor) min(Bi,⌊ViremC⌋) 数量的这种货币,设为 t m p i tmp_i tmpi ,并让 r e m C remC remC 减去 t m p i × V i tmp_i×V_i tmpi×Vi .( B i B_i Bi 先不修改,放在最后确定了以这种方式支付的次数以后再修改)
-
第一次遍历每种货币结束后,如果 r e m C > 0 remC>0 remC>0 则开始反向遍历,否则直接执行第 3 3 3 步。第二次遍历中,贪心地按价值小到价值大的顺序考虑。在考虑到第 i i i 种货币时,设该种货币的剩余数量为 B i B_i Bi ,第一次遍历时使用了 t m p i tmp_i tmpi 枚,只要发现 B i > t m p i B_i>tmp_i Bi>tmpi ,即在第一次遍历中这种货币执行的是富裕支付,没有用完,则多使用一枚这种硬币,并令 t m p i tmp_i tmpi 加 1 1 1 , r e m C remC remC 减去 V i V_i Vi ,此时一定有 r e m C ≤ 0 remC≤0 remC≤0 ,故跳出第二次遍历。
-
如果 r e m C ≤ 0 remC≤0 remC≤0 ,则找到了当前状态下的最优支付方式,即第 i i i 种货币使用 t m p i tmp_i tmpi 枚,那么接下来我们要求出以这种支付方式能够支付的次数,设为 c n t cnt cnt ,则 c n t = m i n ( ⌊ B i t m p i ⌋ ) cnt=min(\lfloor \frac{B_i}{tmp_i} \rfloor) cnt=min(⌊tmpiBi⌋)(即取所有 ⌊ B i t m p i ⌋ \lfloor \frac{B_i}{tmp_i} \rfloor ⌊tmpiBi⌋ 中最小的) ,也就是以该种支付方式支付时,能够支付最少次数的货币的支付次数。接下来让 A n s w e r Answer Answer 加上 c n t cnt cnt ,并对于每一种货币,让 B i B_i Bi 减去 t m p i tmp_i tmpi ,然后返回第 1 1 1 步,接着寻找新的状态下的最优支付方式。 如果 r e m C > 0 remC>0 remC>0 , 就说明无论如何也无法完成一次支付了,输出 A n s w e r Answer Answer 并退出。
证明:
由于价值大的货币的价值一定可以被价值小的货币的价值整除,所以第一次遍历种的贪心策略是易证的,在不浪费的前提下,一定要优先使用价值大的货币。
进入第二次遍历的条件,也就是执行完第一次遍历后
r
e
m
C
>
0
remC>0
remC>0 的条件,是在价值较大的货币支付完后,在最后一次富裕支付发生后(不妨设为这次支付用的是按价值排序后的第
k
k
k 种货币),即第
k
k
k 种货币支付后,有
r
e
m
C
<
V
k
remC<V_k
remC<Vk ,也就是说再多使用一枚货币
k
k
k 就会发生浪费的情况下,剩余部分
r
e
m
C
remC
remC 即便穷尽所有比这种货币小的货币也无法支付,也就是说,此时所有比这种货币价值小的货币能支付的价值总和是小于
r
e
m
C
remC
remC 的,而
r
e
m
C
remC
remC 又是小于
V
k
V_k
Vk 的,因此在最后一次富裕支付发生后,如果剩下的小价值货币无法支付
r
e
m
C
remC
remC ,那么这些小价值货币就已经都没有用了,因为之前的价值大的每种货币都做到了不浪费情况下的最满支付,故从此以后不论用不用这些小价值货币,都必须多用一枚货币
k
k
k 了,同时由于货币
k
k
k 在第一次遍历时达到了不浪费情况下的最满支付,此时多使用一枚货币
k
k
k 一定可以达成
r
e
m
C
≤
0
remC≤0
remC≤0 的要求(事实上一定会有
r
e
m
C
<
0
remC<0
remC<0 )。
因此,这种贪心策略是正确的。
下面贴代码:
#include <cstdio>
#include <cstring>
#include <climits>
#include <algorithm>
using namespace std;
typedef long long ll;
const int MAX_N = 22;
struct P
{
ll V;
ll B;
bool operator< (const P& _Right)const
{
return V < _Right.V;
}
P() :V(0), B(0) {}
P(const ll& V, const ll& B)
:V(V), B(B) {}
};
P v[MAX_N];
ll tmp[MAX_N];
int main()
{
int N; ll C;
scanf("%d%lld", &N, &C);
for (int i = 0; i < N; ++i)
{
scanf("%lld%lld", &v[i].V, &v[i].B);
}
sort(v, v + N);
ll ans = 0;
while (N) // 首先用掉价值大于C的货币
{
if (v[N - 1].V >= C)
{
ans += v[--N].B;
}
else break;
}
for (;;)
{
memset(tmp, 0, sizeof(tmp));
ll remC = C;
for (int i = N - 1; i >= 0; --i)
{
P& cur = v[i];
if (cur.B * cur.V >= remC)
{
tmp[i] = remC / cur.V;
}
else
{
tmp[i] = cur.B;
}
remC -= tmp[i] * cur.V;
}
if (remC > 0)
{
for (int i = 0; i < N; ++i)
{
P& cur = v[i];
if (cur.B > tmp[i]) // 如果使用这种货币在第一轮扫描中没有用完, 则多用一个这种货币就可以支付, 同时比这价值小
{ // 的货币都没有用处了, 因为所有比这种货币价值小的货币的价值总和也不如一枚这种货币的价值高
++tmp[i];
remC -= cur.V;
break;
}
}
}
if (remC > 0) // 无论如何都已无法再次支付
{
break;
}
ll cnt = LLONG_MAX;
for (int i = 0; i < N; ++i) // 计算以这种方式足够支付的周数
{
if (tmp[i])
{
cnt = min(cnt, v[i].B / tmp[i]);
}
}
ans += cnt;
for (int i = 0; i < N; ++i)
{
v[i].B -= cnt * tmp[i];
}
}
printf("%lld\n", ans);
return 0;
}