装箱问题
Description
有一个箱子容量为v(正整数,0≤v≤20000),同时有n个物品(0<n≤30),每个物品有一个体积(正整数)。
要求从m个物品中,任取若千个装入箱内,使箱子的剩余空间为最小。
Input
第一行,一个整数v,表示箱子容量;
第二行,一个整数,表示有n个物品;
接下来n行,每行一个数,分别表示这n个物品的各自体积。
Output
输出一行,一个整数,表示箱子剩余空间。
Sample Input
24
6
8
3
12
7
9
7
Sample Output
0
方法1(动态规划)
思路:
我们把第n个数字当作阶段,把某一个数能否被已有的数字组成当作状态。
我们设:新组成的数字=a
原有的数字=b
读入的数字=c
那么有:a=b+c
我们可以用一个bool数组储存我们构成的数字,每读入一个新数字就循环把新数字加上原有的数字。
注意循环枚举原有数字时要用逆循环。
原因:这道题中每个数字只能使用一次。
若使用正循环,不可避免的在这次循环中刚加入数组的数字也会被当成原有的数字,那么同一个数字就会被加上多次,不符合题意。
因为c>0,所以a>b,所以使用逆循环就是从大往小找,就可以保证不会把刚加入数组的数字当成原有的数字,每个数也就只会被使用一次。(若题目中每个数都能使用多次,那么只需要把逆循环改为正循环)
代码
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n,m,sum[31];
bool puts[20001];
memset(puts,false,sizeof(puts));
puts[0]=true;//边界值
scanf("%d %d",&n,&m);
for(int i=1;i<=m;i++)
{
scanf("%d",&sum[i]);
for(int j=n;j>=0;j--)
if(puts[j]==true)//若j出现过
if(j+sum[i]<=n)
puts[j+sum[i]]=true;
}
for(int i=n;i>=0;i--)//找最小值
if(puts[i]==true)
{
printf("%d",n-i);
return 0;
}
}
方法2(队列)
把已经找到的数放入队列,每次只要访问这个队列,并把队列里每个数都加上刚读入的数就可得到新数字。
方法1中的数字是否使用一次的问题:每次确定把tail的值赋给tail2,保证从head访问到tail2,而每次加数字时都把tail+1,这样就可保证每个数字只使用一次。(若每个数字可使用多次,就从head访问到tail)
代码
#include<bits/stdc++.h>
using namespace std;
int n,v,k,tail=1,tail2,head=1,ans,num[200010];
bool f[20010];
int main()
{
memset(f,false,sizeof(f));
f[0]=true;//边界值
num[tail]=0;//边界值
ans=2147483647;//无穷大
scanf("%d %d",&v,&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&k);
tail2=tail;
for(int j=head;j<=tail2;j++)
if(f[num[j]+k]==false&&(num[j]+k)<=v)//(num[j]+k)未出现过且在范围内
{
tail++;//尾节点++
num[tail]=num[j]+k;
f[num[j]+k]=true;
ans=min(ans,(v-num[j]-k));//取最小值
}
}
printf("%d",ans);
return 0;
}