问题描述:
有N件物品和一个容积为M的背包。第i件物品的体积w[i],价值是d[i]。求解将哪些物品装入背包可使价值总和最大。每种物品只有一件, 可以选择放或者不放(N<=3500,M <= 13000)。
输入:
第一行表示物品数量N和容积M;之后N行,分别表示第i个物品的体积和价值;
输出:
输出可放入背包的最大价值总和。
输入样例:
3 5
1 3
4 5
3 6
输出样例:
9
思路:
1.和问题“神奇的口袋”(详见:程序设计与算法(二)算法基础7.3神奇的口袋)类似,遇到物品i后,可以选择放入,也可以选择不放入,因此money(N,M)=max(money(N-1,M-v[i])+d[i],money(N-1,M));
2.方法1:递归(思路较容易):子问题:前n件物品占据m个容积时的最大价值;money(n,m)=max(money(n-1,m),money(n-1,m-v[n])+d[i])前者是没放入第n件,后者是放入第n件;
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
int m,n,d[3501],v[3501];//d数组表示价值,v数组表示体积
int money(int n,int m)
{
if(n==1){//递归边界:仅剩1个物品时,看看是否能放进去
if(v[1]<=m)return d[1];
else return 0;
}
else if(v[n]<m){//先看第n能否放进去,如果可以则取较大值
return max(money(n-1,m),(money(n-1,m-v[n])+d[n]));
}
else return money(n-1,m);
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>v[i]>>d[i];
cout<<money(n,m)<<endl;
}
3.方法2:滚动数组方法(思路较难),解法与递归法类似,money(n,m)=max(money(n-1,m),money(n-1,m-v[n])+d[i]),只是这里的money为一维数组out;采用双重循环,外层循环(i)表示只利用前i个物品,内层循环(j,由大到小很重要,详见“4”)表示占据的空间为j;
4.out[j]的值与自身(没有把第i个物品放进去,体积j不变,价值不变,即:自身)和out[j-v[i]]+d[i](把第i个物品放进去,体积j变为j-v[i],价值变为out[j-v[i]]+d[i])有关,而下表分别是j和j-v[i],含有比自己小的数,所以如果从小到大循环,到达out[j]的时候,前面的数早已发生了变化,所以必须从大到小循环;
#include<cstdio>
#include<iostream>
using namespace std;
int m,n,d[3501],v[3501];//d数组表示价值,v数组表示体积
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>v[i]>>d[i];
int out[m+1]={0};//out数组表示,占据体积为i时的最大价值
for(int i=1;i<=n;i++){
for(int j=m;j>=0;j--){
if(j>=v[i])
out[j]=max(out[j],(out[j-v[i]]+d[i]));
}
}
cout<<out[m+1]<<endl;
}
5.如图所示:虚线部分为模拟(假想)出的数组,只有实线的一行是占据空间的,第i行第j列的各个数据要么是继承正上方i-1行j列所对应列的数据,要么就是取的i-1行第j列前数据,取决于两个数据的大小;
6.大费周章的使用滚动数组的目的是减少内存占用(3500x13000的空间太大)。