传送门:点这里
题意:用所给的的大小为 2 的非负整数次幂的物品把背包装满。
首先我们需要知道一点:物品可以对半拆分。那么这意味什么呢?就是可以把物品拆分成最小的单位,即大小为 1。
于是,我们就可以用这些物品来表示1—sum(物品总和)中的任意一个数的大小,最多只是拆分次数的不同。
思路:先计算出所有物品的总和,如果背包的大小比物品的总和大,显然是不能装满的。接下来就是位运算和贪心了,具体怎么实现请看下面。
首先,我们要把背包装满,而背包的大小是一个数。一个数是可以被拆成二进制数表示的。
例如:样例一中的10,可以拆成1010。
既然背包大小可以被拆成二进制数表示的,物品大小也是如此。
我们把每个物品拆成二进制数,因为物品大小是为 2 的非负整数次幂,所以我们只要找到最高位的 1 就行。
开一个数组来记录物品在二进制位上的数量。
例如:8 大小这个物品,二进制数为 1000,它在第4位上为 1 ,第4位上的数字就代表了有几个8 大小的物品。
接下来就是贪心,其实我更感觉像模拟。
从第一位开始,每次先用物品在这位上数量减去背包在这位上的0或1。如果物品在这位上数量小于 0,从下一位调一个过来(就像减法中借位操作),不管下一位有没有,先减 1再说,但如果物品在这位上数量是大于 2 的,那么我就可以把2个这个位进1下个位了,即有 2 进 1。最后直到 n 是小于0的,但注意n 小于0并不是结束,我们要把位的欠的债还清,结束条件是n 小于0并且该位大于等于0。
代码如下:
#include <bits/stdc++.h>
#define ll long long
#define mem(a,b) memset(a,b,sizeof(a))
#define fo1(a,b) for(int a=0;a<b;++a)
#define fo2(a,b) for(int a=1;a<=b;++a)
#define inf 0x3f3f3f3f
using namespace std;
const int maxn=1e4+5;
const int mod=1e9+7;
int ans[65];
int main()
{
int t,m,te;
ll n;
cin>>t;
while(t--){
cin>>n>>m;
ll sum=0;
mem(ans,0);
fo1(i,m){
scanf("%d",&te);
sum+=te;
int p=0;
while((te>>p)>1)
p++;
ans[p]++;
}
if(sum<n){
cout<<-1<<endl;
continue;
}
int index=0,num=0;
while(n||ans[index]<0){
ans[index]-=n&1;
if(ans[index]<0){
ans[index+1]--;
num++;
}
else
ans[index+1]+= ans[index]>>1;
n>>=1;
index++;
}
cout<<num<<endl;
}
return 0;
}