题目大意:
有一个大小为n的背包,有m个大小为2i(i=0,1,2,3...)的物品,并且每个物品可以分成大小相同的两份,可以一直分成到大小为1为止,问能否用这些物品把背包恰好填满,如果能填满,输出拆分物品的最小次数。
解题思路:
通过二进制的方法,从高位到低位贪心的一位一位处理。
比如样例一:
10 3
1 32 1
n=10,转换成二进制为1010,所以我们看成是1000+10.
三个物品转换成二进制分别为:1 100000 1
那么我们用一个dp[i]来表示第i位上有多少个1,比如上述的三个物品转换成dp数组就是dp[0]=2,dp[5]=1,其余都为0。
那么我们只需要从n对应的二进制的第0位开始与dp[]进行比较,如果dp[i]上的数量可以满足n的要求,则直接减去,否则就需要拆分较大的物品,即为向上借位。
具体可以看代码及其注释
1 #include <iostream> 2 #include <cstring> 3 #include <string> 4 #include <algorithm> 5 #include <queue> 6 #include <stack> 7 #include <stdio.h> 8 #include <cmath> 9 #include <string.h> 10 11 using namespace std; 12 #define ll long long 13 static const int WHITE=0; 14 static const int GRAY=1; 15 static const int BLACK=2; 16 static const int INF=0x3f3f3f3f; 17 ll Pow(ll a,ll b,ll mod){if(b==0) return 1%mod; ll sum=1; a=a%mod; while(b>0) { if(b%2==1) sum=(sum*a)%mod; b/=2; a=(a*a)%mod;}return sum;} 18 int dp[35];//用来记录第i位上有多少个1 19 void change(int a)//由于物品的大小都是2的n次方,所以其二进制一定为1000---的形式 20 { 21 int k=0; 22 while((a>>k)>1)//找到该数的二进制的1在第几位 23 k++; 24 dp[k]++; 25 } 26 int main() 27 { 28 freopen("C:\Users\16599\Desktop\in.txt","r",stdin); 29 int T; 30 cin>>T; 31 while (T--) 32 { 33 memset(dp,0,sizeof(dp)); 34 ll n,m,sum; 35 sum=0; 36 cin>>n>>m; 37 for(int i=1;i<=m;i++) 38 { 39 int a; 40 cin>>a; 41 sum+=a; 42 change(a); 43 } 44 if(sum<n)//全部装进都装不满 45 { 46 cout<<"-1"<<endl; 47 continue; 48 } 49 int p=0,num=0; 50 while(n||dp[p]<0) 51 { 52 dp[p]-=n&1;//n&1是用来判断n转换成二进制后的最低位是否为1 53 if(dp[p]<0)//如果物品的对应位置不能满足n的需求,则需要向高位借位 54 { 55 dp[p+1]--; 56 num++;//较大物品拆分,次数加一 57 } 58 else 59 dp[p+1]+=dp[p]/2;//如果低位有的多,可以给高位用 60 n>>1;//n的最低为处理完成,进一位 61 p++; 62 } 63 cout<<num<<endl; 64 } 65 66 return 0; 67 }