Allowance
有n种数字,第i种数字值为\(v_i\),有\(b_i\)个,保证随i的增大而增大,且对于任意i有\(a_{i-1}|a_i\)(显然,\(i\in(1,n]\)),现求将它们划分成最多的组数,并且保证每一组的数字的和大于等于c;\(n\leq 20,c,v\leq 10^8\)。
解
显然要分的组数最多,要能够做到拿出一组中的一个最小的数字,让其不满足条件,即填到尽量满,显然每一组都要尽可能做到。
于是我们从大往小填一组,如果当前枚举的数字为i,如果不选i,而转而选比i小的数字,容易发现因为整除的性质,那些比i小的数字必然会凑成i,更进一步,也就是选i的决策包含了不选i的决策,于是i一定要选。
因此我们就得到了一个凑的满满的组,显然再向其中填入一个数字,一定会超过c,容易知道,要让组数最大,显然需要填入一个当前能填的最小数字,这样满足了一组的和尽可能小,所以最优。
于是我们得到一个做法,从大到小枚举数字,填到不能填为止(这里应该用同余的知识优化,而不是一个一个选),再从小到大选择一个数字,如果总和大于等于c,则++ans。
考虑进一步优化,我们显然一个做法下来,得到了一个组的分配方法,于是我们可以copy,这样就可以做到很快了。
参考代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define il inline
#define ri register
#define Size 25
#define intmax 0x7fffffff
using namespace std;
struct coin{
int v,b;
il bool operator<(const coin&a)const{
return v<a.v;
}
}co[Size];
int ct,a[Size];
int main(){
int n,c,ans(0);
scanf("%d%d",&n,&c);
for(int i(1),v,b;i<=n;++i){
scanf("%d%d",&v,&b);
if(v>=c)ans+=b;
else co[++ct]={v,b};
}sort(co+1,co+ct+1);
while(true){int X(c);
for(int i(ct),j;i;--i){
j=min(X/co[i].v,co[i].b);
a[i]=j,X-=j*co[i].v,co[i].b-=j;
}
if(X)for(int i(1);i<=ct;++i)
if(co[i].b){++a[i],--co[i].b,X-=co[i].v;break;}
if(X>0)break;++ans;int opt(intmax);
for(int i(1);i<=ct;++i)
if(a[i])opt=min(opt,co[i].b/a[i]);ans+=opt;
for(int i(1);i<=ct;++i)co[i].b-=opt*a[i];
}printf("%d",ans);
return 0;
}