题目大意
有个人有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");
}
反思
一开始有方向没有坚持下去,而是去看题解,英语水平又有限,搞了好久。
不能贪快就看题解,没有好处,浪费时间。
要想清楚再打,现在代码能力比较差。