CSP初赛复习-28-动态规划-多重背包
背包问题
01 背包
每种物品只有一个,每件物品只有选与不选两种状态
完全背包
每种物品有无限个
多重背包
每种物品有多个,并且多种物品数量不完全相同
分组背包
按组打包,每组最多一个
多重背包1 数据量较小
问题描述
现有 N件物品和一个容量为V的背包,每种物品有s[i]件,第 i种物品的体积是 v[i],价值是 w[i],在背包能承受的范围内,试问将哪些物品装入背包后可使总价值最大,求这个最大价值
输入格式
第一行两个整数,N和V用空格隔开,分别表示物品种数和背包容积 ( 0<=n,m<=100)
接下来有N行每行三个整数,v[i],w[i],s[i],用空格隔开,分别表示第i件物品的体积、价值和每种物品的数量
( 0<=v[i],w[i],s[i]<=100)
输出格式
输出一个整数,表示最大价值
输入样例
4 5
1 2 3
2 4 1
3 4 3
4 5 2
输出样例
10
动态规划 -二维数组朴素算法 三层循环
大致思路
1 问题分解成N * V个子问题,N * V个状态
2 状态表示 f[i] [j] 前i种物品装入总体积不超过j的背包的最大价值
3 状态转移i-1种物品到i种物品装入背包体积为j时 分为 i为0 ,1...s[i]时的最大价值
因此需要通过需要逐一判断取最大
for(int k=0;k<=s[i];k++)
参考程序
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 110;//数组存储数量最大值物品最大数量 和 背包最大容积都是maxn
int N,V;//N种物品 背包最大容积
//v[i]每种物品的体积 w[i]每种物品的价值 s[i]每种物品的件数
int v[maxn],w[maxn],s[maxn];
int f[maxn][maxn];//f[i][j]表示装入前i种物品不超过体积j的最大价值
int main(){
cin>>N>>V;//输入N种物品和背包最大容积V
for(int i=1;i<=N;i++){//输入每种物品的体积 价值 数量
cin>>v[i]>>w[i]>>s[i];
}
for(int i=1;i<=N;i++){//枚举物品1~N件和体积组合
//计算物品和背包容积0~V整数组合计算所有最大价值
for(int j=0;j<=V;j++){//前i种物品最大容积不超过j时 计算最大体积
//枚举每种物品的件数
for(int k=0;k<=s[i];k++){//k从0开始,为0相等于01背包不选的情况
if(j>=v[i]*k){//每种逐一放入,放入此种物品件数不能超过背包总容量j
f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+w[i]*k);
}
}
}
}
cout<<f[N][V];//装入N种物品背包体积不超过V的最大价值
return 0;
}
一维滚动数组优化
大致思路
1 由状态转移方程可知
f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+w[i]*k);
二维表格中,第i行只和i-1行有关
计算第j列只与j列前面的列(j-k * v[i])有关
因此,可以使用一维数组替换二维数组进行优化,可以提供存储效率
2 i行计算时使用i-1行,需要先计算后面的(如果先计算前面的,则i-1行被覆盖了) 需要从大到小循环
for(int j=V;j>=0;j--)
3 二维变一维主要存储空间重复利用,大大减少了,时间复杂度没有大幅度减少,因此这种实现方式下一题会超时
参考程序
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 110;//数组存储数量最大值物品最大数量 和 背包最大容积都是maxn
int N,V;//N种物品 背包最大容积
//v[i]每种物品的体积 w[i]每种物品的价值 s[i]每种物品的件数
int v[maxn],w[maxn],s[maxn];
int f[maxn];//f[j]表示不超过体积j的最大价值
int main(){
cin>>N>>V;//输入N种物品和背包最大容积V
for(int i=1;i<=N;i++){//输入每种物品的体积 价值 数量
cin>>v[i]>>w[i]>>s[i];
}
for(int i=1;i<=N;i++){//枚举物品1~N件和体积组合
//计算物品和背包容积0~V整数组合计算所有最大价值(使用i-1,所以滚动数组从后向前)
for(int j=V;j>=0;j--){//前i种物品最大容积不超过j时计算最大价值
//枚举每种物品的件数
for(int k=0;k<=s[i];k++){//k从0开始,为0相等于01背包不选的情况
if(j>=v[i]*k){//每种逐一放入,放入此种物品件数不能超过背包总容量j
f[j]=max(f[j],f[j-k*v[i]]+w[i]*k);
}
}
}
}
cout<<f[V];//装入N种物品背包体积不超过V的最大价值
return 0;
}
多重背包2 数据量大
问题描述
现有 N件物品和一个容量为V的背包,每种物品有s[i]件,第 i种物品的体积是 v[i],价值是 w[i],在背包能承受的范围内,试问将哪些物品装入背包后可使总价值最大,求这个最大价值
输入格式
第一行两个整数,N和V用空格隔开,分别表示物品种数和背包容积 ( 0<=n,m<=1000)
接下来有N行每行三个整数,v[i],w[i],s[i],用空格隔开,分别表示第i件物品的体积、价值和每种物品的数量
( 0<=v[i],w[i],s[i]<=2000)
输出格式
输出一个整数,表示最大价值
输入样例
4 5
1 2 3
2 4 1
3 4 3
4 5 2
输出样例
10
二进制分堆 时间复杂度大大降低
时间复杂度
O(n^2 * logS)
大致思路
1 在二维数组朴素算法 三层循环实现中,如果把第3层循环数大大降低,时间复杂度就会大大降低
2 分堆思路
示例1
7件物品,分堆如下
1 2 4
2^0 2^1 2^2
示例2
8件物品,分堆如下
1 2 4 1
2^0 2^1 2^2 8-2^0-2^1-2^2
示例3
10件物品,分堆如下
1 2 4 3
2^0 2^1 2^2 10-2^0-2^1-2^2
3 分堆后,把每堆看做一件物品的01背包,按01背包动态规划思路解决
4 如下使用01背包的一维数组优化思路
参考程序
#include<iostream>
using namespace std;
const int maxn=1010,maxm=2010;
int N,V;//n件物品 V背包可装物品体积
//v[i]物品分堆后的每堆体积 w[i]物品分堆后的每堆价值
int v[maxn],w[maxn];
int f[maxm];//f[j]循环过程种外层i堆物品 体积不超过j的最大价值
int main(){
cin>>N>>V;
int cnt=1;
for(int i=1;i<=N;i++){//n件物品
//a单件物品的体积 b单件物品的价值 s有几件
int a,b,s;
cin>>a>>b>>s;
int k=1;//当前分堆的数量 按二进制权递增1 2 4 8
while(k<=s){//如果需要分堆数量k<剩余件数s,继续分
v[cnt]=a*k;//k件的体积
w[cnt]=b*k;//k件的价值
s-=k;//当前物品总件数件数分出去的k件
k*=2;//k增加一倍
cnt++;//总堆数加1
}
/*
上面while循环最后一次退出前有2种情况
s=k 这时s件物品正好通过二进制拆分完
s<k 这时s没被分完 while循环结束后,会出现s>0
*/
if(s>0){//s>0时分最后一堆
v[cnt]=s*a;//剩余s件的体积
w[cnt]=s*b;//剩余s件的价值
//总堆数加1,最后一堆还加1是为下一件物品增加数组下标
cnt++;//最后一件物品的最后一堆多加了1 所以总堆数为cnt-1
}
}
/*
n件物品分了cnt-1堆 所以准确说是i<cnt 不是i<=cnt
使用i<=cnt对最值结果也不会有影响,结果也是对的
*/
for(int i=1;i<cnt;i++){//n件物品分组后可以看作01背包处理
for(int j=V;j>=v[i];j--){//按体积从大到小递进 V~v[i] ,j<v[i] f[]下标为负数无意义
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
}
cout<<f[V]<<endl;//背包不超过体积V的最大价值
return 0;
}
CSP初赛复习-28-动态规划-练习题
https://www.cnblogs.com/myeln/articles/17626730.html
作者:newcode 更多资源请关注纽扣编程微信公众号
从事机器人比赛、机器人等级考试、少儿scratch编程、信息学奥赛等研究学习