洛谷 P2376 [USACO09OCT]津贴Allowance 解题报告

P2376 [USACO09OCT]津贴Allowance

题目描述

作为创造产奶纪录的回报,\(Farmer\) \(John\)决定开始每个星期给\(Bessie\)一点零花钱。

\(FJ\)有一些硬币,一共有\(N(1<=N<=20)\)种不同的面额。每一个面额都能整除所有比它大的面额。

他想用给定的硬币的集合,每个星期至少给\(Bessie\)某个零花钱的数目\(C(1<=C<=100000000)\)。请帮他计算他最多能支付多少个星期的零花钱。

输入输出格式

输入格式:

第1行: 两个由空格隔开的整数:\(N\)\(C\)

第2到第\(N+1\)行: 每一行有两个整数表示一个面额的硬币:硬币面额\(V(1<=V<=100,000,000)\)拥有的该面额的硬币数\(B(1<=B<=1,000,000)\)

输出格式:

第1行: 一个单独的整数,表示最多能给支付多少个星期至少为\(C\)的零用钱。


一开始的贪心很容易想到要每次先给大钱,如果不够一步步拿小钱补充。

但最后小钱用完了可能产生浪费,万一大钱浪费一下可以更少呢。又看了看数据范围\(N(1<=N<=20)\),心想怕不是个搜索。憋了一会儿搜索写不出来。最后看了题解才知道是贪心。

先说说这个题贪心的思维导向性在哪,没错就是这句话“每一个面额都能整除所有比它大的面额”,是不是感觉又奇怪又违和,感觉用不上??

一般来讲,遇到这种看起来比较怪的条件,可以尝试这向贪心的方面想一想。哪怕证不出来也没关系,骗点分总不亏撒


下面正题,贪心策略及证明

策略

每一次给钱时,从大钱开始给,但每次给到要浪费钱的一次就不给了,用小一些的钱给。
给到已经没有小钱了以后,再给怎么也会产生浪费,就从小到大给,用面值尽可能小的钱产生浪费

总结起来就是一句话:当需要产生浪费时,用面值尽可能小的钱产生

证明

命题:大钱产生的浪费一定不比小钱小

证明:
任取两个面值的钱分别为\(ka,a\)\(k\)是正整数,在当前次还需要支付零用钱至少\(X\)

(1) 当浪费大钱\(ka\)
\(X=b*ka+r\)
则浪费的钱数为\(f=ka-r\)

(2)当浪费小钱\(a\)
用掉一定的\(ka\)却不浪费当前次还需要支付的零用钱为\(X'=r\)
\(X'=b'*a+r'\)
则浪费的钱数为\(f'=a-r'\)

两者做差,\(f-f'=(k-1)*a+r'-r\)

由③得,\(r'-r=-b'*a\)

\(f-f'=(k-b'-1)*a\)

因为\(k,b'\)均为正整数且\(k>b'\),所以\(f-f'>=0\)

命题得证。


当然,我们肯定不能一次次的枚举每一周,否则会\(T\)两组。

考虑对情况进行加速,说是加速,其实也就是存储每周在某种情况下每个钱用了多少张,然后直接统计这种用钱情况可以重复多少次而已。

写的时候注意细节啊


Code:

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#define ll long long
using namespace std;
const ll N=23;
const ll inf=0x3f3f3f3f3f3f3f3f;
pair <ll ,ll > money[N];
ll n,c,now,ans,cnt[N],l,r;
int main()
{
    scanf("%lld%lld",&n,&c);
    for(ll i=1;i<=n;i++)
        scanf("%lld%lld",&money[i].first,&money[i].second);
    sort(money+1,money+1+n);
    l=1,r=n;
    while(233)
    {
        memset(cnt,0,sizeof(cnt));
        now=c;
        for(ll i=r;i>=l;i--)
        {
            if(!now) break;
            if(now>=money[i].first)
            {
                cnt[i]=now/money[i].first;
                if(cnt[i]<=money[i].second)
                     now=now%money[i].first;
                else
                {
                    cnt[i]=money[i].second;
                    now=now-money[i].first*cnt[i];
                }
            }
        }
        if(now)
        {
            ll L=l;
            while(now)
            {
                if(now<=money[L].first*(money[L].second-cnt[L]))
                {
                    cnt[L]+=now/money[L].first+(now%money[L].first?1:0);
                    if(!money[L].second) L++;
                    now=0;
                }
                else
                {
                    now-=money[L].first*(money[L].second-cnt[L]);
                    cnt[L]=money[L].second;
                    L++;
                }
                if(L>r) break;
            }
        }
        if(!now)
        {
            ll cntt=inf;
            for(ll i=l;i<=r;i++)
                if(cnt[i])
                    cntt=min(cntt,money[i].second/cnt[i]);
            ans+=cntt;
            for(ll i=l;i<=r;i++)
            {
                money[i].second-=cntt*cnt[i];
                if(!money[l].second) l++;
            }
            while(r>=l&&!money[r].second) r--;
        }
        else
            break;
    }
    printf("%lld\n",ans);
    return 0;
}

2018.7.1

posted @ 2018-07-01 16:10  露迭月  阅读(328)  评论(0编辑  收藏  举报