0%

[题解]2019.11.5 背包

[题解]2019.11.5 背包

题意

给定一个01背包和n个物品,背包的容积为k,求有多少种选择方法使得背包再也放不下余下的任意物品。

分析

此题用动规做法,用f[j]表示装满j体积的方案数,那么因为要求的是求不能再放下其他物品的方案数,假设第i个物品为不能放下的物品中体积最小的,那么体积比第i个物品还小的肯定都已经放入了背包中,因为此时背包不能放下第i个物品,所以背包还剩余的体积有可能是0到v[i]-1,这样的话只需要计算出用比v[i]大的物品填满k-(比v[i]小的物品的体积总和)-j(j从0-v[i]-1枚举)的空间的方案数就行了。

到这一步大概已经看出,使用这样的算法需要用排序来使v[i]数组有序,我们从小到大排序后,从后向前枚举i,这样就可以保证在跑用比v[i]大的物品装满j体积空间是无后效性(不太好懂的话可以看看代码辅助理解),同时用一个sum保存前i-1个物品的体积和来提高效率(sum的初始值为所有物品的总和,每枚举到一个i,sum-=v[i]就可以使sum记录的是前i-1个物品的体积和)。

这部分的代码如下

累计答案
1
2
3
4
for(int j=0;j<v[i]&&k-sum-j>=0;j++)//判断防止越界
{
ans+=f[k-sum-j];
}
状态转移
1
2
3
4
for(int j=k;j>=v[i];j--)//跟背包一样
{
f[j]+=f[j-v[i]];
}

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
//此处应开long long 不过貌似数据太弱没开就过掉了
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;

int n,k;
int v[1010],f[1010];
int ans,sum;

int main()
{
#ifndef WDC
freopen("problem.in","r",stdin);
freopen("problem.out","w",stdout);
#endif
int t;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&k);
sum=0;
ans=0;
memset(f,0,sizeof(f));
for(int i=1;i<=n;i++)
{
scanf("%d",&v[i]);
sum+=v[i];
}
f[0]=1;
sort(v+1,v+n+1);
for(int i=n;i>=1;i--)
{
sum-=v[i];
for(int j=0;j<v[i]&&k-sum-j>=0;j++)
{
ans+=f[k-sum-j];
}
for(int j=k;j>=v[i];j--)
{
f[j]+=f[j-v[i]];
}
}
printf("%d\n",ans);
}
#ifndef WDC
fclose(stdin);
fclose(stdout);
#endif
return 0;
}