[CF725E]Too Much Money

题目大意

有个人有n个硬币,每个面值为a[i],要想凑够c面值。
他每次从剩下硬币中取出一个面值最大的,满足它的面值加上之前选的不超过c。
现在你要证明他的方法是有问题的,你在原来基础上给他任意面值(正整数)任意多个硬币,让他的方法凑不出来c。
求你给出硬币的和的最小值。数据保证解不为0。
n,c<=200,000
CF机子2秒时限

分析

一个性质:你只用给他一个硬币就够了。为什么呢?假如你给他2个硬币,面值分别为x,y,那么等价于给他一个x+y面值的。
首先x,y都是在他选择的硬币队列里面,那么给他x+y面值的硬币其实相当于让他提前把x,y面值的硬币都拿走,那么之前要选的还是要选。
CF机子考虑暴力。枚举放进去的硬币面值,然后呢?直接O(N)模拟显然不行。
考虑现在还有s的面值要填,我们肯定是要选a中小于s最大的面值嘛,如果O(1)找到就好了。那么我们预处理一个数组next[x]记录小于等于x最大的硬币是哪个,记得一开始把相同面值的合并一下。
假设我们枚举答案后,模拟的时候跑了k次,我们选出来的面值和,最少也是k(k+1)/2的,那么模拟时间就是根号的了。
总时间复杂度 O(NN)

代码

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define fo(i,j,k) for(i=j;i<=k;i++)
#define fd(i,j,k) for(i=j;i>=k;i--)
const int N=200005;
int a[N],b[N],tt,i,j,k,n,c,next[N],z,decl,nex;
int main()
{
//  freopen("725.in","r",stdin);
    scanf("%d",&c);
    scanf("%d",&n);
    fo(i,1,n) scanf("%d",a+i);
    sort(a+1,a+1+n);
    while (a[n]>c) n--;
    fo(i,1,n)
    {
        j=i;
        while (a[j+1]==a[i]&&j<n)  j++;
        a[++tt]=a[i];
        b[tt]=j-i+1;
        i=j;
    }
    j=tt;
    fd(i,c,1)
    {
        while (a[j]>i) j--;
        next[i]=j;
    }
    fo(i,1,c)
    {
        z=c;
        j=tt;
        if (i>a[j]) z-=i;
        while (z&&j)
        {
            z-=min(b[j],z/a[j])*a[j];
            nex=min(next[z],j-1);
            if (a[j]>=i&&a[nex]<=i)
                if (z>=i) z-=i;
            j=nex;
        }
        if (z) break;
    }
    if (i<=c)
        printf("%d\n",i);
    else printf("Greed is good\n");
}

反思

一开始有方向没有坚持下去,而是去看题解,英语水平又有限,搞了好久。
不能贪快就看题解,没有好处,浪费时间。
要想清楚再打,现在代码能力比较差。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值