题目大意:给定一个容量为V(题目里是D)的背包和N(题目里是V)件物品各自的体积,求有多少种方法使得背包再也装不下任何物品。
思路:既然要使背包装不下任何物品,也就是说这时剩下的物品中体积最小的那一件也不能装入背包,所以就可以枚举剩下的最小体积的这件物品来进行动态规划了。
定义dp[i]=装满体积为i的背包的方法数,volumn[i]=第i件物品的体积,sum[i]=前i件物品的体积之和。
现在要枚举剩下的物品中体积最小的那一件了。先将所有物品按体积大小排序。
首先明确,当第i件物品作为剩下的体积最小的物品时,前i-1件物品就都已经被放到背包里面去了。这时,背包剩余体积为V-sum[i-1];由于不能让第i件物品放入背包中,这时需要的是把体积为V-sum[i-1]-volume[i]+1 ~ V-sum[i-1]放满的方法数,(当然V-sum[i-1]-volume[i]+1可能小于0,要处理一下,k = max(V-sum[i-1]-volume[i]+1,0).)也就是dp数组所对应的那几个数字的和。
如果从体积小的物品开始枚举,考虑当第i件物品不能放入背包的情况,此时,前i-1件物品就都已经被放到背包里面去了,那么就需要计算第i+1 ~ n件物品占据体积k ~ V-sum[i-1]的方法数,然后再在总方法数上加上dp数组对应的值。那么,第i件物品就被考虑了i-1次,此时的算法复杂度为O(N^2 * V)。
为了使得每件物品只被放入到背包一次,考虑从体积大的物品开始枚举。当第i件物品不能放入背包中,而前i件物品都放入了背包中,这时,我们把已知的i+1 ~ N件物品占据体积k ~ V-sum[i-1]的方法数加到总的方法数ans上,然后再去取第i件物品做01背包,供考虑下一件物品不能放入背包的情况使用,直到枚举完全部的物品。
如果你要问我,使用同样的方法来顺序枚举可以不可以,那这个很容易否定。因为在逆序枚举的时候,当第i件物品放不下的时候,第i件物品后的物品都被考虑过了,且第i件物品之后的物品也肯定放不下。而顺序枚举的话,第i件物品之前的物品都被考虑过作为当前放不下的最小物品了,第i件物品放不下不意味着前面被考虑过的i-1件物品放不下,这就违背我们当初的假设了,如果这么做了,还有装进背包的物品的方法就也被考虑进去了。
下面贴一下用降序(逆序枚举)做的代码:
1 #include <iostream> 2 #include <cstring> 3 #include <cstdio> 4 #include <algorithm> 5 using namespace std; 6 7 int dp[1010],volume[35],sum[35]; 8 9 inline int max(int a,int b) 10 { 11 return a > b ? a : b; 12 } 13 14 int main() 15 { 16 int ncase,N,V,num; 17 scanf("%d",&ncase); 18 for(num = 1;num <= ncase;num++) 19 { 20 scanf("%d%d",&N,&V); 21 memset(sum,0,sizeof(sum)); 22 memset(dp,0,sizeof(dp)); 23 24 dp[0] = 1; 25 26 int i,j,ans = 0; 27 for(i = 1;i <= N;i++) 28 scanf("%d",&volume[i]); 29 30 sort(volume + 1,volume + 1 + N); 31 32 for(i = 1;i <= N;i++) 33 sum[i] = sum[i - 1] + volume[i]; 34 35 for(i = N;i >= 1;i--) 36 { 37 int k = max(0,V - sum[i - 1] - volume[i] + 1); 38 39 for(j = V - sum[i - 1];j >= k;j--) 40 ans += dp[j]; 41 42 for(j = V;j >= volume[i];j--) 43 dp[j] += dp[j - volume[i]]; 44 } 45 46 if(volume[1] > V) ans = 0; 47 48 printf("%d %d\n",num,ans); 49 } 50 return 0; 51 }