NOIP2001 普及组 装箱问题
[NOIP2001 普及组] 装箱问题
题目描述
有一个箱子容量为 $V$,同时有 $n$ 个物品,每个物品有一个体积。
现在从 $n$ 个物品中,任取若干个装入箱内(也可以不取),使箱子的剩余空间最小。输出这个最小值。
输入格式
第一行共一个整数 $V$,表示箱子容量。
第二行共一个整数 $n$,表示物品总数。
接下来 $n$ 行,每行有一个正整数,表示第 $i$ 个物品的体积。
输出格式
- 共一行一个整数,表示箱子最小剩余空间。
样例 #1
样例输入 #1
1 | 24 |
样例输出 #1
1 | 0 |
提示
对于 $100\%$ 数据,满足 $0<n \le 30$,$1 \le V \le 20000$。
【题目来源】
NOIP 2001 普及组第四题
题解
题目解析
一个简化的01背包问题,今天刚学的背包问题,作为例题所作.这个问题如果按照一个一个列的话可能到$2^{30}$个可能,事实上我们仅需考虑其前面最多可能组成的可能性.
举一个例子:
1 | 背包大小为10,其中有5个物品,其重量为:1 2 3 4 5. |
最后我们可以得到状态转移方程(f[i][j]存的是bool类型):
文字解释:前i个数字组成价值为j的背包=前i-1个数字组成价值为j的背包||前i-1个数字组成价值为j-a[i]的背包.
举个例子:
1 | 背包大小为20,其中有3个物品,其重量为:1 5 8. |
AC代码
1 |
|
Remark
关于这块代码
1
2
3
4
5
6
7for(int j=0;j<=v;j++){
if(j < a[i]){
f[i%2][j] = f[(i-1)%2][j];
}else{
f[i%2][j] = f[(i-1)%2][j] || f[(i-1)%2][j-a[i]];
}
}这块有两种解释:
- 如果
j < a[i]
那么j-a[i]
会小于0,所以需要分开情况讨论 j < a[i]
的部分中需要把之前存的f[i-1][j]
的情况原封不动放到f[i][j]
中,j >= a[i]
的部分既需要原封不动的存下来,也需要考虑j-a[i]
的情况.
- 如果
i的存在仅依靠与i-1的存在,i+1的存在与i-1并无关系,所以仅需要i=2就可以全部存下来了,所以用了
i%2
和(i-1)%2
就可以了.当然,我们可以对二维数组进一步优化,直接使用一维数组进行滚动刷新.
1
2
3
4for(int i=1;i<=n;i++){
for(int j=a[i];j<=v;j++)
f[j] = f[j] || f[j-a[i]];
}但是这个代码会出现一个bug:
1
2
3
4
5
6假如背包大小为20,其中有3个物品,其重量为:1 5 8.
当i=2时:
for(int j=a[i];j<=v;j++)会不断循环:
f[6] = f[1+5] = 1; f[11] = f[6+5] =1; f[17] = f[11+5] = 1;
造成其能组成 6(1+5) 11(1+5+5) 17(1+5+5+5) ...的情况,也就是加了多次.
实际上它仅能组成 6 (1+5)的背包,多出去的都是加重了的情况.如何解决呢?这就用到了一个小技巧:
倒序递归:
1
2
3
4for(int i=1;i<=n;i++){
for(int j=v;j>=a[i];j--)
f[j] = f[j] || f[j-a[i]];
}手动走一遍递归,看看是不是不会出现重复的情况了?
AC代码:
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
using namespace std;
int f[20010],a[40];
int main(){
int v,n;
cin >> v >> n;
for(int i=1;i<=n;i++){
cin >> a[i];
}
f[0] = 1;
for(int i=1;i<=n;i++){
for(int j=v;j>=a[i];j--)
f[j] = f[j] || f[j-a[i]];
}
int ans = 0;
for(int i=v;i>=0;i--){
if(f[i]){
ans = i;
break;
}
}
cout << v - ans;
return 0;
}
Others
艹,过几天就要省赛了,结果背包还刚学.啊?我刚大一啊,那没事了.
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Xunyoyo!