主要参考内容如下
算法竞赛入门经典(第二版)
完全&多重背包问题
讲述了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
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<
方法2
//状态转移方程为d(i,j)=max(d(i-1,j),d(i-1,j-v[i])+w[i]),即将前i个物品放入背包的最大价值
# include
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<
对方法2进行优化,边读边计算,使用滚动数组,将二维变为一维
# include
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<
下面是完全背包
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
# include
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="<
方法2递推 状态转移方程为d(i)=max{d(i+1)+c[i]}
# include
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="<
方法3//边读边计算
# include
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="<
下面多重背包
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
# include
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<
方法2滚动数组
# include
# include
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<
优化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
# include
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<