https://vjudge.net/problem/CodeForces-1303D
题目大意:你有一个大小为
n
n
n的背包,
m
m
m个盒子,第
i
i
i个盒子的大小为
a
i
a_i
ai,保证
a
i
a_i
ai均为
2
2
2的幂次,每次操作可以选取任意一个盒子并把它分成两个大小相等的盒子,问经过多少次操作后你可以用其中一部分盒子填满背包,若无解输出
−
1
-1
−1。
思路:贪心,考虑 n n n的二进制的第 i i i位,要么由现在已经有的盒子凑出 2 i 2^i 2i,要么划分 a j > 2 i a_j>2^i aj>2i的盒子,因为前者对答案没有贡献,所以优先使用这种方法,如果凑不出来的话只能采用后者,为了使答案最小我们肯定要找到 > 2 i >2^i >2i且最小的 a j a_j aj进行划分;如果这两种方法都不可行的话,说明无解。
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn=1e5+5;
ll n;
int t,m,a[maxn],cnt[70];
//cnt[i]记录大小为2^i的盒子的个数
int main()
{
scanf("%d",&t);
while(t--)
{
memset(cnt,0,sizeof(cnt));
ll sum=0;
scanf("%lld%d",&n,&m);
for(int i=0;i<m;i++)
{
scanf("%d",&a[i]);
sum+=a[i];
}
if(sum<n)
printf("-1\n");
else if(sum==n)
printf("0\n");
else
{
int ans=0;
bool flag=1;
for(int i=0;i<m;i++)
cnt[(int)(log2(a[i])+0.5)]++;
for(ll i=1,j=0;i<=n&&flag;i<<=1,++j)
{
if(n&i)
{
if(cnt[j])//用已经有的盒子
--cnt[j];
else
{
bool tag=0;
for(int k=j+1;k<60;k++)//找到一个盒子进行划分
{
if(cnt[k])
{
tag=1,--cnt[k],ans+=k-j;
while(--k>j)
++cnt[k];
break;
}
}
flag=tag;
}
}
cnt[j+1]+=cnt[j]>>1;//两个小盒子合成一个大盒子
}
if(!flag)
ans=-1;
printf("%d\n",ans);
}
}
return 0;
}