01背包,完全背包,多重背包,混合背包

主要参考内容如下
算法竞赛入门经典(第二版)
完全&多重背包问题
讲述了01背包,完全背包,多重背包之间的关系
背包九讲-整合版
讲述的全面,但有些细节都是默认你是知道的
01背包,完全背包,多重背包,混合背包详解
使用伪码,进行介绍
习题来自信息学奥赛
如有侵权,请联系我删除。
下面见习题

1267:【例9.11】01背包问题

时间限制: 1000 ms         内存限制: 65536 KB
提交数: 9143     通过数: 5641 
【题目描述】
一个旅行者有一个最多能装 M 公斤的背包,
现在有 n 件物品,它们的重量分别是W1,W2,...,Wn,它们的价值分别为C1,C2,...,Cn,
求旅行者能获得最大总价值。
【输入】
第一行:两个整数,M(背包容量,M≤200)和N(物品数量,N≤30);

第2..N+1行:每行二个整数Wi,Ci,表示每个物品的重量和价值。

【输出】
仅一行,一个数,表示最大总价值。

方法1

//状态转移方程为d(i,j)=max(d(i+1,j),d(i+1,j-v[i])+w[i]),即将第 【i,n】个物品放入背包的最大价值
# include<iostream>
using namespace std;
int m,n;//背包容量,背包数量
int w[30+5];   //存放每个物品的重量	
int c[30+5]; //存放每个物品的价值
int d[30+5][200+5]; 
int main()
{
	cin>>m>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>w[i]>>c[i];
	}
	for(int i=n;i>=1;i--)   //d(i,j)=max(d(i+1,j),d(i+1,j-v[i])+w[i])
	{
		for(int j= 1;j<=m;j++)
		{
			d[i][j]=(i==n? 0:d[i+1][j]);
			if(j>=w[i])  d[i][j]=max(d[i][j],d[i+1][j-w[i]]+c[i]);
		}
	}
	cout<<d[1][m]<<endl;
	return 0;
 } 

方法2

//状态转移方程为d(i,j)=max(d(i-1,j),d(i-1,j-v[i])+w[i]),即将前i个物品放入背包的最大价值
# include<iostream>
using namespace std;
int m,n;//背包容量,背包数量
int w[30+5];   //存放每个物品的重量	
int c[30+5]; //存放每个物品的价值
int d[30+5][200+5]; 
int main()
{
	cin>>m>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>w[i]>>c[i];
	}
	for(int i=1;i<=n;i++)   //d(i,j)=max(d(i-1,j),d(i-1,j-v[i])+w[i])
	{
		for(int j= 1;j<=m;j++)
		{
			d[i][j]=(i==1? 0:d[i-1][j]);
			if(j>=w[i])  d[i][j]=max(d[i][j],d[i-1][j-w[i]]+c[i]);
		}
	}
	cout<<d[n][m]<<endl;
	return 0;
 } 

对方法2进行优化,边读边计算,使用滚动数组,将二维变为一维

# include<iostream>
using namespace std;
int m,n;//背包容量,背包数量
int d[200+5]; 
int main()
{
	cin>>m>>n;
	int w,c;
	for(int i=1;i<=n;i++)   //d(i,j)=max(d(i-1,j),d(i-1,j-v[i])+w[i])
	{
		cin>>w>>c;
		for(int j=m;j>=1;j--)
		{
			d[j]=(i==1? 0:d[j]);
			if(j>=w)  d[j]=max(d[j],d[j-w]+c);
		}
	}
	cout<<d[m]<<endl;
	return 0;
 }

下面是完全背包

1268:【例9.12】完全背包问题
时间限制: 1000 ms         内存限制: 65536 KB
提交数: 7819     通过数: 4192 
【题目描述】
设有n种物品,每种物品有一个重量及一个价值。但每种物品的数量是无限的,同时有一个背包,最大载重量为M,今从n种物品中选取若干件(同一种物品可以多次选取),使其重量的和小于等于M,而价值的和为最大。

【输入】
第一行:两个整数,M(背包容量,M≤200)和N(物品数量,N≤30);

第2..N+1行:每行二个整数Wi,Ci,表示每个物品的重量和价值。

【输出】
仅一行,一个数,表示最大总价值。

状态转移方程可定义为d(i)=max(d(i+1)+c[i]);即将第 【i,n】个物品放入背包的最大价值
或者d(i)=max(d(i-1)+c[i]); 前i个物品装进背包的最大价值

方法1递归法 状态转移方程d(i)=max{d(i+1)+c[i]}

# include<iostream>
# include<cstring>
using namespace std;
int n,m;//n背包容量,m物品数
int w[30+5];  //重量
int c[30+5];//价值
int d[200+5];
bool vis[200+5];    //标记是否已计算
int dp(int s)
{
	
	if(vis[s]) return d[s];
	vis[s]=1;
	int &ans=d[s];
	ans=0;
	for(int j=1;j<=m;j++)
	     if(s>=w[j]) ans=max(ans,dp(s-w[j])+c[j]);
	return ans;
 } 
int main()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		cin>>w[i]>>c[i];
	}
	memset(vis,0,sizeof(vis));
    memset(d,0,sizeof(d));
	   dp(n);
	cout<<"max="<<d[n]<<endl;
	return 0;
}

方法2递推 状态转移方程为d(i)=max{d(i+1)+c[i]}

# include<iostream>
using namespace std;
int m,n;//背包容量,背包数量
int d[200+5];
int w[30+5];
int c[30+5]; 
int main()
{
	cin>>m>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>w[i]>>c[i];
	}
	
	for(int i=n;i>=1;i--)   //d(i)=max{d(i+1)+c[i]}
	{
		for(int j=1;j<=m;j++)
		{
			d[j]=(i==n?0:d[j]);
			if(j>=w[i])  d[j]=max(d[j],d[j-w[i]]+c[i]);
		}	
	}
	cout<<"max="<<d[m]<<endl;
	return 0;
}

方法3//边读边计算

# include<iostream>
using namespace std;
int m,n;//背包容量,背包数量
int d[200+5];
int w[30+5];
int c[30+5]; 
int main()
{
	cin>>m>>n;
	int w,c; 
	for(int i=1;i<=n;i++)   //d(i)=max{d(i-1)+c[i]}  前i个物品装进背包的最大价值 
	{
		cin>>w>>c;
		for(int j=1;j<=m;j++)
		{
			d[j]=(i==1?0:d[j]);
			if(j>=w)  d[j]=max(d[j],d[j-w]+c);
		}	
	}
	cout<<"max="<<d[m]<<endl;
	return 0;
}

下面多重背包

1269:【例9.13】庆功会

时间限制: 1000 ms         内存限制: 65536 KB
提交数: 6179     通过数: 3516 
【题目描述】
为了庆贺班级在校运动会上取得全校第一名成绩,班主任决定开一场庆功会,为此拨款购买奖品犒劳运动员。期望拨款金额能购买最大价值的奖品,可以补充他们的精力和体力。

【输入】
第一行二个数n(n≤500),m(m≤6000),其中n代表希望购买的奖品的种数,m表示拨款金额。

接下来n行,每行3个数,v、w、s,分别表示第I种奖品的价格、价值(价格与价值是不同的概念)和能购买的最大数量(买0件到s件均可),其中v≤100,w≤1000,s≤10。

【输出】
一行:一个数,表示此次购买能获得的最大的价值(注意!不是价格)。

【输入样例】

方法一
第i种物品的数量为num,那么对于第i种物品就可选0,1,num个
使用二维矩阵

# include<iostream>
# include<cstring>
using namespace std;
int n,m;//奖品的总数,拨款金额
int v[500+5];//价格 
int w[500+5];//价值
int s[500+5] ;  //最大数量
int d[500+5][6000+5];	 
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		cin>>v[i]>>w[i]>>s[i];
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=m;j>=1;j--)
		{
		   d[i][j]=(i==1? 0:d[i-1][j]);
		   	for(int k=0;k<=s[i];k++) 
		{    if(j>=k*v[i])
			{
				d[i][j]=max(d[i][j],d[i-1][j-k*v[i]]+k*w[i]);
				}
			}
		}
	}
	cout<<d[n][m]<<endl;
	return 0;
}

方法2滚动数组


# include<iostream>
# include<cstring>
using namespace std;
int n,m;//奖品的总数,拨款金额
int v[500+5];//价格 
int w[500+5];//价值
int s[500+5] ;  //最大数量
//int d[500+5][6000+5];
int d[6000+5];	 
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		cin>>v[i]>>w[i]>>s[i];
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=m;j>=1;j--)
		{
		   d[j]=(i==1? 0:d[j]);
		   	for(int k=0;k<=s[i];k++)  //可取[0,s[i]]; 
			{
			if(j>=k*v[i])
			{
				d[j]=max(d[j],d[j-k*v[i]]+k*w[i]);
				}
			}
		}
	}
	cout<<d[m]<<endl;
	return 0;
}

优化1:二进制优化

见参考内容

优化2:单调队列

见参考内容

混合背包就是将前面3种背包混合;那么可把每一种背包编写一个函数模块
if 01背包
call 01背包模块
else if完全背包
call 完全背包模块
else if 多重背包
call 多重背包模块

1270:【例9.14】混合背包

时间限制: 1000 ms         内存限制: 65536 KB
提交数: 4185     通过数: 2463 
【题目描述】
一个旅行者有一个最多能装V公斤的背包,现在有n件物品,它们的重量分别是W1,W2,...,Wn,它们的价值分别为C1,C2,...,Cn。有的物品只可以取一次(01背包),有的物品可以取无限次(完全背包),有的物品可以取的次数有一个上限(多重背包)。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

【输入】
第一行:二个整数,M(背包容量,M≤200),N(物品数量,N≤30);

第2..N+1行:每行三个整数Wi,Ci,Pi,前两个整数分别表示每个物品的重量,价值,第三个整数若为0,则说明此物品可以购买无数件,若为其他数字,则为此物品可购买的最多件数(Pi)。
# include<iostream>
# include<cstring>
using namespace std;
int m,n;//背包容量,物品数量
int w[30+5],c[30+5],num[30+5];  //重量,价值,数量 
int d[200+5];
int main()
{
	cin>>m>>n;
	for(int i=1;i<=n;i++)
		cin>>w[i]>>c[i]>>num[i];
	memset(d,0,sizeof(d));
	for(int i=1;i<=n;i++)
	{
			if(!num[i]||num[i]*w[i]>m)   //完全背包 
			{
				for(int j=1;j<=m;j++)   //注意顺序 
				{
				if(j>=w[i])
			   		d[j]=max(d[j],d[j-w[i]]+c[i]);
				}
			}
			else
			{
				for(int j=m;j>=1;j--)    //注意顺序 
					for(int k=0;k<=num[i];k++)     //多重背包和01背包都按01背包进行展开 
					{
					if(j>=k*w[i])
					   d[j]=max(d[j],d[j-k*w[i]]+k*c[i]);
					}
				}
		}
	cout<<d[m]<<endl;
	return 0;
 } 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值