程序设计与算法(二)算法基础7.4背包问题

问题描述:

有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的空间太大)。

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值